Skip to content

Commit 98a99cf

Browse files
committed
Ensured dismiss is possible when a popover is being presented and the other way around: both wait until the other operation is complete before starting the new transition.
1 parent 58d4d20 commit 98a99cf

File tree

5 files changed

+204
-6
lines changed

5 files changed

+204
-6
lines changed
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Created by Werner Altewischer on 25/04/2017.
3+
// Copyright (c) 2017 Werner IT Consultancy. All rights reserved.
4+
//
5+
6+
#import <Foundation/Foundation.h>
7+
8+
@interface NSCondition (WEPopover)
9+
10+
/**
11+
Waits in a background thread until the specified condition is broadcast/signalled and the specified predicate is true using appropriate thread safe locking techniques.
12+
13+
Using this method is assured that the wait stops when this object (self) is deallocated
14+
15+
Returns NO iff the predicate returned true without having to wait, YES if the wait actually had to occur.
16+
17+
@see broadcastCondition:forPredicateModification:
18+
*/
19+
- (BOOL)weWaitForPredicate:(BOOL (^)(void))predicate completion:(void (^)(BOOL waited))completion;
20+
21+
/**
22+
Waits with a timeout, specify timeout <= 0.0 to wait indefinitely.
23+
24+
The completion block has a BOOL argument predicateResult which is true if the predicate evaluated to true within the timeout period and false otherwise.
25+
26+
Returns NO iff the predicate returned true without having to wait, YES if the wait actually had to occur.
27+
*/
28+
- (BOOL)weWaitForPredicate:(BOOL (^)(void))predicate timeout:(NSTimeInterval)timeout completion:(void (^)(BOOL predicateResult, BOOL waited))completion;
29+
30+
/**
31+
Performs a thread safe predicate modification while broadcasting the condition which is paired to it.
32+
*/
33+
- (void)weBroadcastForPredicateModification:(void (^)(void))modification;
34+
35+
@end
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//
2+
// Created by Werner Altewischer on 25/04/2017.
3+
// Copyright (c) 2017 Werner IT Consultancy. All rights reserved.
4+
//
5+
6+
#import "NSCondition+WEPopover.h"
7+
8+
@implementation NSCondition (WEPopover)
9+
10+
- (BOOL)weWaitForPredicate:(BOOL (^)(void))predicate completion:(void (^)(BOOL waited))completion {
11+
return [self weWaitForPredicate:predicate timeout:0.0 completion:^(BOOL predicateResult, BOOL waited) {
12+
if (completion) {
13+
completion(waited);
14+
}
15+
}];
16+
}
17+
18+
- (BOOL)weWaitForPredicate:(BOOL (^)(void))predicate timeout:(NSTimeInterval)timeout completion:(void (^)(BOOL predicateResult, BOOL waited))completion {
19+
return [self weWaitForPredicate:predicate timeout:timeout completion:completion timeoutOccured:NO waited:NO];
20+
}
21+
22+
- (BOOL)weWaitForPredicate:(BOOL (^)(void))predicate timeout:(NSTimeInterval)timeout completion:(void (^)(BOOL predicateEvaluation, BOOL waited))completion timeoutOccured:(BOOL)timeoutOccured waited:(BOOL)waited {
23+
24+
BOOL predicateResult = NO;
25+
26+
[self lock];
27+
predicateResult = predicate();
28+
[self unlock];
29+
30+
if (predicateResult || timeoutOccured) {
31+
if (completion) {
32+
completion(predicateResult, waited);
33+
}
34+
return NO;
35+
} else {
36+
37+
NSDate *expirationDate = nil;
38+
if (timeout > 0.0) {
39+
expirationDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
40+
}
41+
42+
id __weak weakSelf = self;
43+
44+
[self wePerformBlockInBackground:^id {
45+
BOOL predicateResult1 = NO;
46+
[weakSelf lock];
47+
while (weakSelf != nil && (predicateResult1 = predicate()) == NO && (expirationDate == nil || [expirationDate timeIntervalSinceNow] > 0.0)) {
48+
if (expirationDate == nil) {
49+
//Wait for max 1 second to be able to check again if weakSelf != nil (object could be deallocated).
50+
[weakSelf waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
51+
} else {
52+
[weakSelf waitUntilDate:expirationDate];
53+
}
54+
}
55+
[weakSelf unlock];
56+
return @(predicateResult1);
57+
} withCompletion:^(id resultFromBlock) {
58+
BOOL timeoutOccured1 = ![resultFromBlock boolValue];
59+
[weakSelf weWaitForPredicate:predicate timeout:timeout completion:completion timeoutOccured:timeoutOccured1 waited:YES];
60+
}];
61+
return YES;
62+
}
63+
}
64+
65+
/**
66+
Performs a thread safe predicate modification while broadcasting the condition which is paired to it.
67+
*/
68+
- (void)weBroadcastForPredicateModification:(void (^)(void))modification {
69+
[self lock];
70+
modification();
71+
[self broadcast];
72+
[self unlock];
73+
}
74+
75+
- (void)wePerformBlock:(id (^)(void))block onQueue:(dispatch_queue_t)queue withCompletion:(void (^)(id resultFromBlock))completion {
76+
if (block) {
77+
dispatch_async(queue, ^{
78+
id result = block();
79+
if (completion) {
80+
dispatch_async(dispatch_get_main_queue(), ^{
81+
completion(result);
82+
});
83+
}
84+
});
85+
}
86+
}
87+
88+
- (void)wePerformBlockInBackground:(id (^)(void))block withCompletion:(void (^)(id resultFromBlock))completion {
89+
[self wePerformBlock:block onQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) withCompletion:completion];
90+
}
91+
92+
@end

Classes/Popover/WEPopoverController.h

+17-6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ typedef void(^WEPopoverTransitionBlock)(WEPopoverTransitionType transitionType,
136136
*/
137137
@property (nonatomic, readonly, getter=isDismissing) BOOL dismissing;
138138

139+
/**
140+
* Convenience getter, returns true if presenting or dismissing.
141+
*/
142+
@property (nonatomic, readonly, getter=isTransitioning) BOOL transitioning;
143+
139144
/**
140145
The view from which the popover was presented.
141146
@@ -335,7 +340,8 @@ typedef void(^WEPopoverTransitionBlock)(WEPopoverTransitionType transitionType,
335340
336341
The view and rect are used as anchor for automatic reposition during rotation.
337342
338-
This method has no effect is the popover is already visible, being presented or dismissed.
343+
This method has no effect is the popover is already visible.
344+
If the popover is transitioning, this method waits untill the existing animation completes before starting the new presentation.
339345
*/
340346
- (void)presentPopoverFromRect:(CGRect)rect
341347
inView:(UIView *)view
@@ -346,7 +352,8 @@ typedef void(^WEPopoverTransitionBlock)(WEPopoverTransitionType transitionType,
346352
/**
347353
Presents the popover from the specified bar button item and allowed arrow directions, optionally animating the presentation.
348354
349-
This method has no effect is the popover is already visible, being presented or dismissed.
355+
This method has no effect is the popover is already visible.
356+
If the popover is transitioning, this method waits untill the existing animation completes before starting the new presentation.
350357
*/
351358
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)item
352359
permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections
@@ -357,7 +364,8 @@ typedef void(^WEPopoverTransitionBlock)(WEPopoverTransitionType transitionType,
357364
358365
Calls the completion block when done.
359366
360-
This method has no effect is the popover is already visible, being presented or dismissed.
367+
This method has no effect is the popover is already visible.
368+
If the popover is transitioning, this method waits untill the existing animation completes before starting the new presentation.
361369
*/
362370
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)item
363371
permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections
@@ -369,7 +377,8 @@ typedef void(^WEPopoverTransitionBlock)(WEPopoverTransitionType transitionType,
369377
370378
For more control, assign the contentViewController and call one of the other reposition methods manually.
371379
372-
This method has no effect if the popover is currently dismissing or hasn't been presented yet.
380+
This method has no effect if the popover hasn't been presented yet.
381+
If the popover is transitioning, this method waits untill the existing animation completes before starting the new presentation.
373382
*/
374383
- (void)repositionForContentViewController:(UIViewController *)vc animated:(BOOL)animated;
375384

@@ -378,7 +387,8 @@ typedef void(^WEPopoverTransitionBlock)(WEPopoverTransitionType transitionType,
378387
379388
The reposition is optionally animated.
380389
381-
This method has no effect if the popover is currently dismissing or hasn't been presented yet.
390+
This method has no effect if the popover hasn't been presented yet.
391+
If the popover is transitioning, this method waits untill the existing animation completes before starting the new presentation.
382392
*/
383393
- (void)repositionPopoverFromRect:(CGRect)rect
384394
inView:(UIView *)view
@@ -391,7 +401,8 @@ typedef void(^WEPopoverTransitionBlock)(WEPopoverTransitionType transitionType,
391401
The reposition is optionally animated.
392402
The completion block is called when done.
393403
394-
This method has no effect if the popover is currently dismissing or hasn't been presented yet.
404+
This method has no effect if the popover hasn't been presented yet.
405+
If the popover is transitioning, this method waits untill the existing animation completes before starting the new presentation.
395406
*/
396407
- (void)repositionPopoverFromRect:(CGRect)rect
397408
inView:(UIView *)view

Classes/Popover/WEPopoverController.m

+54
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#import "WEPopoverContainerView.h"
1414
#import "WEWeakReference.h"
1515
#import "UIView+WEPopover.h"
16+
#import "NSCondition+WEPopover.h"
1617

1718
static const NSTimeInterval kDefaultPrimaryAnimationDuration = 0.3;
1819
static const NSTimeInterval kDefaultSecundaryAnimationDuration = 0.15;
@@ -25,6 +26,7 @@ @interface WEPopoverController()<WETouchableViewDelegate>
2526
@property (nonatomic, assign, getter=isDismissing) BOOL dismissing;
2627
@property (nonatomic, assign, getter=isPopoverVisible) BOOL popoverVisible;
2728
@property (nonatomic, assign) CGSize effectivePopoverContentSize;
29+
@property (nonatomic, strong) NSCondition *transitioningCondition;
2830

2931
@end
3032

@@ -41,6 +43,7 @@ - (CGRect)collapsedFrameFromFrame:(CGRect)frame forArrowDirection:(UIPopoverArro
4143
- (UIView *)fillBackgroundViewWithDefault:(UIView *)defaultView;
4244
- (CGRect)calculatedContainerViewFrame;
4345
- (void)keyViewDidLayoutSubviewsNotification:(NSNotification *)notification;
46+
- (void)waitUntilNotTransitioningWithCompletion:(void (^)(BOOL waited))completion;
4447

4548
@end
4649

@@ -197,6 +200,7 @@ - (id)init {
197200
self.primaryAnimationDuration = kDefaultPrimaryAnimationDuration;
198201
self.secundaryAnimationDuration = kDefaultSecundaryAnimationDuration;
199202
self.gestureBlockingEnabled = YES;
203+
self.transitioningCondition = [NSCondition new];
200204
}
201205
return self;
202206
}
@@ -214,6 +218,30 @@ - (void)dealloc {
214218

215219
#pragma mark - Getters/setters
216220

221+
- (void)setPresenting:(BOOL)presenting {
222+
if (_presenting != presenting) {
223+
[self.transitioningCondition weBroadcastForPredicateModification:^{
224+
self->_presenting = presenting;
225+
}];
226+
}
227+
}
228+
229+
- (void)setDismissing:(BOOL)dismissing {
230+
if (_dismissing != dismissing) {
231+
[self.transitioningCondition weBroadcastForPredicateModification:^{
232+
self->_dismissing = dismissing;
233+
}];
234+
}
235+
}
236+
237+
- (BOOL)transitioning {
238+
return _presenting || _dismissing;
239+
}
240+
241+
- (BOOL)isTransitioning {
242+
return [self transitioning];
243+
}
244+
217245
- (void)setContentViewController:(UIViewController *)vc {
218246
[self setContentViewController:vc animated:NO];
219247
}
@@ -330,6 +358,13 @@ - (void)presentPopoverFromRect:(CGRect)rect
330358
animated:(BOOL)animated
331359
completion:(WEPopoverCompletionBlock)completion {
332360

361+
if (self.isDismissing) {
362+
__typeof(self) __weak weakSelf = self;
363+
[self waitUntilNotTransitioningWithCompletion:^(BOOL waited) {
364+
[weakSelf presentPopoverFromRect:rect inView:theView permittedArrowDirections:arrowDirections animated:animated completion:completion];
365+
}];
366+
}
367+
333368
if (!self.isPresenting && !self.isDismissing && ![self isPopoverVisible]) {
334369
self.presenting = YES;
335370

@@ -653,6 +688,14 @@ - (void)determineContentSizeWithConstraintSize:(CGSize)constraintSize {
653688
}
654689

655690
- (void)dismissPopoverAnimated:(BOOL)animated userInitiated:(BOOL)userInitiated completion:(WEPopoverCompletionBlock)completion {
691+
692+
if (self.isPresenting) {
693+
__typeof(self) __weak weakSelf = self;
694+
[self waitUntilNotTransitioningWithCompletion:^(BOOL waited) {
695+
[weakSelf dismissPopoverAnimated:animated userInitiated:userInitiated completion:completion];
696+
}];
697+
}
698+
656699
if (self.containerView && !self.isDismissing && !self.isPresenting) {
657700
self.dismissing = YES;
658701
[self.containerView resignFirstResponder];
@@ -865,5 +908,16 @@ - (void)keyViewDidLayoutSubviewsNotification:(NSNotification *)notification {
865908
[self repositionContainerViewForFrameChange];
866909
}
867910

911+
- (void)waitUntilNotTransitioningWithCompletion:(void (^)(BOOL waited))completion {
912+
__typeof(self) __weak weakSelf = self;
913+
[self.transitioningCondition weWaitForPredicate:^BOOL {
914+
return !weakSelf.isTransitioning;
915+
} completion:^(BOOL waited) {
916+
if (completion) {
917+
completion(waited);
918+
}
919+
}];
920+
}
921+
868922
@end
869923

WEPopover.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
749FAE881286130000AB97F9 /* WEPopoverController.m in Sources */ = {isa = PBXBuildFile; fileRef = 749FAE841286130000AB97F9 /* WEPopoverController.m */; };
4949
74A2A7B1190E5B800054A6CD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74A2A7B0190E5B800054A6CD /* QuartzCore.framework */; };
5050
A83DF01A644BD2B58C4EDD65 /* WECustomPopoverController.m in Sources */ = {isa = PBXBuildFile; fileRef = A83DF4A31AA1332716629BF2 /* WECustomPopoverController.m */; };
51+
A83DF0E72F611E8D79702E64 /* NSCondition+WEPopover.m in Sources */ = {isa = PBXBuildFile; fileRef = A83DF01961E14C5F2CE7865F /* NSCondition+WEPopover.m */; };
5152
A83DF4665DCA1611146378A7 /* UIView+WEPopover.m in Sources */ = {isa = PBXBuildFile; fileRef = A83DFD2DCC3853D474CD9B5F /* UIView+WEPopover.m */; };
5253
/* End PBXBuildFile section */
5354

@@ -108,6 +109,8 @@
108109
749FAE851286130000AB97F9 /* WEPopoverParentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WEPopoverParentView.h; sourceTree = "<group>"; };
109110
74A2A7B0190E5B800054A6CD /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
110111
8D1107310486CEB800E47090 /* WEPopover-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "WEPopover-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = "<group>"; };
112+
A83DF01961E14C5F2CE7865F /* NSCondition+WEPopover.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSCondition+WEPopover.m"; sourceTree = "<group>"; };
113+
A83DF062174D6D5F0B855E89 /* NSCondition+WEPopover.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSCondition+WEPopover.h"; sourceTree = "<group>"; };
111114
A83DF428564B2075B045155A /* WECustomPopoverController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WECustomPopoverController.h; sourceTree = "<group>"; };
112115
A83DF4A31AA1332716629BF2 /* WECustomPopoverController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WECustomPopoverController.m; sourceTree = "<group>"; };
113116
A83DF7D277F13D75FAC228AD /* UIView+WEPopover.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+WEPopover.h"; sourceTree = "<group>"; };
@@ -261,6 +264,8 @@
261264
749AF5581C7F553B00B444E1 /* WEWeakReference.m */,
262265
A83DFD2DCC3853D474CD9B5F /* UIView+WEPopover.m */,
263266
A83DF7D277F13D75FAC228AD /* UIView+WEPopover.h */,
267+
A83DF01961E14C5F2CE7865F /* NSCondition+WEPopover.m */,
268+
A83DF062174D6D5F0B855E89 /* NSCondition+WEPopover.h */,
264269
);
265270
path = Popover;
266271
sourceTree = "<group>";
@@ -384,6 +389,7 @@
384389
746B0A1D137454A000C5A8B5 /* UIBarButtonItem+WEPopover.m in Sources */,
385390
A83DF01A644BD2B58C4EDD65 /* WECustomPopoverController.m in Sources */,
386391
A83DF4665DCA1611146378A7 /* UIView+WEPopover.m in Sources */,
392+
A83DF0E72F611E8D79702E64 /* NSCondition+WEPopover.m in Sources */,
387393
);
388394
runOnlyForDeploymentPostprocessing = 0;
389395
};

0 commit comments

Comments
 (0)