Search code examples
iosobjective-cuiviewuipangesturerecognizer

How do I get the visible portion of a UIView that is partially panned off the screen?


I have a UIView that I move with a pan gesture to the side of the screen and now the UIView is only partially displayed on the screen. How do I get the CGRect that contains ONLY the visible portion of the UIView, AND, in the coordinates of the original view?

I've tried combinations of CGRectIntersect() for the UIView.frame rect and the [UIScreen mainscreen].bounds rect, like this:

CGRect rect = CGRectIntersection([UIScreen mainScreen].bounds,
                                 view.frame);

I haven't been able to correctly resolve matching the coordinate systems.


Solution

  • After some (hours of) experimentation I came up with this solution:

    // return the part of the passed view that is visible
    - (CGRect)getVisibleRect:(UIView *)view {
        // get the root view controller (and it's view is vc.view)
        UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
    
        // get the view's frame in the root view's coordinate system
        CGRect frame = [vc.view convertRect:view.frame fromView:view.superview];
    
        // get the intersection of the root view bounds and the passed view frame
        CGRect intersection = CGRectIntersection(vc.view.bounds, frame);
    
        // adjust the intersection coordinates thru any nested views
        UIView *loopView = view;
        do {
            intersection = [loopView convertRect:intersection fromView:loopView.superview];
    
            loopView = loopView.superview;
        } while (loopView != vc.view);
    
        return intersection; // may be same as the original view frame
    }
    

    I first tried to convert the root view to the destination view's coordinates and then do the CGRectIntersect on the view frame, but it did not work. But I got it working the reverse way for UIViews with the root view as their superview. Then after some spelunking I figured out that I had to navigate thru the view hierarchy for subviews.

    It works for UIViews where the superview is the root view and also for UIViews that are subviews of other views.

    I tested it by drawing borders around these visible rects on the initial views, and it works perfectly.

    BUT ... it does NOT work correctly if a UIView is scaled (!=1) AND a subview of another UIView other than the root view. The origin of the resultant visible rect is offset by a bit. I tried a few different ways to adjust the origin if the view is in a subview but I couldn't figure out a clean way to do it.

    I've added this method to my utility UIView category, along with all the other "missing" UIView methods I've been developing or acquiring. (Erica Sadun's transform methods ...I'm not worthy...)

    This does solve the problem I was working on though. So I will post another question regarding the scaling problem.

    EDIT: While working on the question and answer for the scaling issue, I came up with a better answer for this question too:

    // return the part of the passed view that is visible
    - (CGRect)getVisibleRect:(UIView *)view {
        // get the root view controller (and it's view is vc.view)
        UIViewController *vc = UIApplication.sharedApplication.keyWindow.rootViewController;
    
        // get the view's frame in the root view's coordinate system
        CGRect rootRect = [vc.view convertRect:view.frame fromView:view.superview];
    
        // get the intersection of the root view bounds and the passed view frame
        CGRect rootVisible = CGRectIntersection(vc.view.bounds, rootRect);
    
        // convert the rect back to the initial view's coordinate system
        CGRect visible = [view convertRect:rootVisible fromView:vc.view];
    
        return visible; // may be same as the original view frame
    }