Understanding coordinates, views, subviews, and orientation changes

1.1k Views Asked by At

So, I am building an iOS app for an iPad. The app has video chat embedded in it. I have gotten video chat, notifications, etc. all to work. My problem is with the interface. I'm a noob when it comes to iOS programming btw; I only started doing this 2 weeks ago. Sorry if the post is a bit long I just attempted to highlight everything that might be at play here and my thought process. Any critique is welcome.

I have a UIWebView (call it webView) that displays most of the information. When a button is clicked, I want that to be resized and a small new view to appear, which has three items: the person you're chatting with (call it imageOne), your video chat (call it imageTwo), and a hang up button (call it buttonHangUp). If the device is in landscape orientation, I want the new view to appear on the right with imageOne, imageTwo, and buttonHangUp in a vertical position. If the device is in a portrait orientation, I want the new view to appear on the bottom, with imageOne, image Two, and buttonHangUp in a horizontal position.

Here is how my thought process went:

I proceeded with the second option (if there is a simpler way of doing this or if I should have done something else, please let me know; I would love to learn the tips and tricks of the trade).

Here is how my code went:

  • I made a global variable isLandscape a la apple tutorial up there
  • I made 6 other global variables, CGRects, each to hold the position of the different items in portrait and landscape (imageOnePortrait, imageOneLandscape, etc)
  • I implemented awakeFromNib, setting the portrait variables to the initial positions and sizes of the items

    (void) awakeFromNib
    {
    isLandscape = NO;
    
    imageOnePortrait = self.imageOne.frame;
    imageTwoPortrait = self.imageTwo.frame;
    buttonHangUpPortrait = self.buttonHangUp.frame;
    
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
    }
    
  • I implemented the orientationChanged method as described in the apple documentation. However, I replaced the UIDeviceOrientation with UIInterfaceOrientation because the UIDeviceOrientation was not sending the landscape/portrait orientations I was looking for, but faceup and facedown messages

    - (void) orientationChanged:(NSNotification *)notification
    {
    UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
    if(UIInterfaceOrientationIsLandscape(interfaceOrientation) && !isLandscape)
    {
        CGRect temp = self.videoView.frame;
        temp.size.width = 360;
        temp.size.height = 748;
        temp.origin.x = 664;
        temp.origin.y = 0;
        self.videoView.frame = temp;
        self.imageOne.frame = imageOneLandscape;
        self.imageTwo.frame = imageTwoLandscape;
        self.buttonHangUp.frame = buttonHangUpLandscape;
        isLandscape = YES;
    }
    else if (UIInterfaceOrientationIsPortrait(interfaceOrientation) && isLandscape)
    {
        CGRect temp = self.videoView.frame;
        temp.size.width = 768;
        temp.size.height = 300;
        temp.origin.x = 0;
        temp.origin.y = 703;
        self.videoView.frame = temp;
        self.imageTwo.frame = imageTwoPortrait;
        self.imageOne.frame = imageOnePortrait;
        self.buttonHangUp.frame = buttonHangUpPortrait;
        isLandscape = NO;
    }
    }
    
  • Under viewDidLoad, I set the coordinates for the Landscape coordinates (I assume these coordinates are loaded before any call to the orientationMade function is ever made, and that they are relative to the videoView - correct me please if I need to set them relative to something else; I see them set relative to videoView in the storyboard):

    imageOneLandscape.origin.x = 20;
    imageOneLandscape.origin.y = 20;
    imageTwoLandscape.origin.x = 20;
    imageTwoLandscape.origin.y = 288;
    buttonHangUpLandscape.origin.x = 20;
    buttonHangUpLandscape.origin.y = 556;
    
  • Under the function that initiates the video call, I resize webView, and show the videoView:

    CGRect temp = self.webView.frame;
    
    if(isLandscape)
    {
        temp.size.width -= self.videoView.frame.size.width;
        temp.origin.x = 0;
        temp.origin.y = 0;
        self.webView.frame = temp;
    } else {
        temp.size.height -= self.videoView.frame.size.height;
        temp.origin.x = 0;
        temp.origin.y = 0;
        self.webView.frame = temp;
    }
    
    self.videoView.hidden = FALSE;
    
  • and then I do the opposite in buttonHangUp function

Several wrong things are happening:

  • When I switch orientations, videoView doesn't always move or resize, and when it moves the move is often delayed (happens a second after the rest of the items have rotated)
  • When I switch to landscape, imageOne and imageTwo are squished horizontally, and are not in the alignment I have set them to be in
  • When I switch to portrait, things seem to be working fine all the time. videoView always goes to the right place and so do its subviews (imageOne, imageTwo, and buttonHangUp).
  • the imageOne and imageTwo disappear from portrait view when I lay the ipad flat, without changing orientation
  • even if I set the views to display in a different layout in viewDidLoad, they are resetting to the layout they have in the storyboard

Could anyone explain to me what is happening here and why it is happening? This is very confusing to me. I think it has to do with coordinate systems switching between coordinates for webView and the app itself, the order of precedence of the different functions executing (awakeFromNib, the view coordinates being set for the first time from the storyboard, viewDidLoad, etc), and with the redrawing of the subviews, or with a combination of all these issues and possibly more. I am going to investigate those issues next.

I would also appreciate someone mentioning what the best practices are for achieving what I want; as I said I am new to this, and apple documentation unfortunately doesn't cover what is considered best practice when it comes to my case. Links to articles would also be appreciated.

2

There are 2 best solutions below

0
Bassel On BEST ANSWER

I fixed my code after some hours of debugging yesterday. Here are all the things that were going wrong, and hopefully a helpful guide to how to handle switching orientation programmatically:

  • First, in awakeFromNib, do NOT put any code that handles cooridinates and locations. awakeFromNib gets called before the view starts to load, so it knows nothing about positions. Do all the coordinate initializations in viewDidLoad.
  • Second, make sure to set autolayout to off, and set the autoresize subviews to off for any container views. This tripped me for a while.
  • Third, use the apple method of calling device orientation described here: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDeviceOrientationChanges/RespondingtoDeviceOrientationChanges.html#//apple_ref/doc/uid/TP40007457-CH7-SW14 . I found that to be much more reliable than the status bar orientation described above.
  • Fourth, and this was wrong in the code above and was an error on my part, make sure to initialize the size of all the components you need to move around. the CGRects above used to move between landscape and portrait have origins defined, but no size, which was causing my views to vanish when set to them.
  • Fifth, and finally, make sure to do the size adjustment to BOTH the landscape and portrait global variables when the slider appears/disappears, because if the device is flipped back with the slider there then by the code above the slider will obscure part of the webView.

I still welcome any comments about how people handle landscape and portrait layouts!

1
Tommy Devoy On

Well you have an insanely long question but I'll give you part and tell you why your device orientations aren't working properly.

Your not specifying the object which to generate notifications for.

Change this:

    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) 
name:UIDeviceOrientationDidChangeNotification object:nil]

To this:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
        [[NSNotificationCenter defaultCenter]
         addObserver:self selector:@selector(orientationChanged:)
         name:UIDeviceOrientationDidChangeNotification
         object:[UIDevice currentDevice]];

Also it's important to note, your not taking advantage of the power of the notification center. Your doing extra work, and not grabbing the actual orientation sent inside of the notification. You don't need to check the UIInterfaceOrientation each time, you can just grab the device orientation from the note like so:

- (void) orientationChanged:(NSNotification *)note
{
    UIDevice *device = note.object;
    switch(device.orientation)//do whatever you want in each orientation
    {
        case UIDeviceOrientationPortrait:
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            break;
        case UIDeviceOrientationLandscapeLeft:
            break;
        case UIDeviceOrientationLandscapeRight:
            break;
        default:
            break;
    };
}