Is there a way to get a notification, a callback or some other means to call a method whenever a UIView becomes visible for the user, i.e. when a UIScrollview is the superview of some UIViews, and the ViewController of such a UIView shall get notified when its view is now visible to the user?
I am aware of the possible, but not so elegant solution of checking to which position the ScrollView scrolled (via UIScrollViewDelegate-methods) and compute if either one of the subviews is visible...
But I'm looking for a more universal way of doing this.
The view's window property is non-nil if a view is currently visible, so check the main view in the view controller: Invoking the view method causes the view to load (if it is not loaded) which is unnecessary and may be undesirable. It would be better to check first to see if it is already loaded.
The UIViewController class defines the shared behavior that's common to all view controllers. You rarely create instances of the UIViewController class directly. Instead, you subclass UIViewController and add the methods and properties needed to manage the view controller's view hierarchy.
A view controller manages a single root view, which may itself contain any number of subviews. User interactions with that view hierarchy are handled by your view controller, which coordinates with other objects of your app as needed. Every app has at least one view controller whose content fills the main window.
I've managed to solve the problem this way:
First, add a category for UIView with the following method:
// retrieve an array containing all super views -(NSArray *)getAllSuperviews { NSMutableArray *superviews = [[NSMutableArray alloc] init]; if(self.superview == nil) return nil; [superviews addObject:self.superview]; [superviews addObjectsFromArray:[self.superview getAllSuperviews]]; return superviews; }
Then, in your View, check if the window-property is set:
-(void)didMoveToWindow { if(self.window != nil) [self observeSuperviewsOnOffsetChange]; else [self removeAsSuperviewObserver]; }
If it is set, we'll observe the "contentOffset" of each superview on any change. If the window is nil, we'll stop observing. You can change the keyPath to any other property, maybe "frame" if there is no UIScrollView in your superviews:
-(void)observeSuperviewsOnOffsetChange { NSArray *superviews = [self getAllSuperviews]; for (UIView *superview in superviews) { if([superview respondsToSelector:@selector(contentOffset)]) [superview addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; } } -(void)removeAsSuperviewObserver { NSArray *superviews = [self getAllSuperviews]; for (UIView *superview in superviews) { @try { [superview removeObserver:self forKeyPath:@"contentOffset"]; } @catch(id exception) { } } }
Now implement the "observeValueForKeyPath"-method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if([keyPath isEqualToString:@"contentOffset"]) { [self checkIfFrameIsVisible]; } }
Finally, check if the view's frame is visible inside the window's frame:
-(void)checkIfFrameIsVisible { CGRect myFrameToWindow = [self.window convertRect:self.frame fromView:self]; if(myFrameToWindow.size.width == 0 || myFrameToWindow.size.height == 0) return; if(CGRectContainsRect(self.window.frame, myFrameToWindow)) { // We are visible, do stuff now } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With