Technology

Subview Rotation on iOS 8 with Swift while keeping static main view

That title is a mouthful.  Basically, I just want to know how to imitate the native iPhone camera app.  The camera button stays locked where the home screen is but the icons rotate with the device as well as the capture area.  Should be pretty simple right? tl;dr:  See the code here on github that I did.

rs

Well with iOS 8 Apple introduced the concept of adaptive layouts.  See WWDC session 216.  This introduced size classes and traits which I think is fantastic.  Except for when you want to imitate the iOS camera application.  Then you don't know what to think. There were two main ideas I could have used that I came across: 1.  Using two windows.  This was brilliant and I think would work.  This even came with sample code to show how to keep the rotations separate.  I had read other people saying it was a bad idea to have two UIWindows.  I played with this a little bit but it seemed too much for what I needed.  Plus, I had the UI Tab Bar controller as the root image so it was somewhat complicated. 2.  UIInterfaceOrientation.  These methods all seem to be depreciated in iOS8 and may or may not work.  The problem with these methods is that the root method gets the notification and then signals to every body else.  I may have been able to work with this but I didn't want to go through all the way down the hierarchy and implement all these methods for those that should be static and those that shouldn't be. I went with UIDevice.orientation. Here's the steps:

1. Subclass UITabBarController

Since my main project has a tab bar as the root interface I started here.  This way I want all the views to be able to rotate and use auto layout to do this.  There's just one subview that needs to stay the same.  This was accomplished by adding the following method to the view controller:
override func shouldAutorotate() -> Bool {
        if (self.selectedIndex == 0){
            return false
        }
        return true
}


This makes it so that this view 1 in the tab bar won't rotate.

2.  Subscribe for notifications in the App Delegate

I may have been able to do this in the main class, but I did it in the app delegate in case it didn't get alerts.  Then I had that propagate another notification.  This may be a redundant step, but figured I'd try it and was too lazy to change it back.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name: UIDeviceOrientationDidChangeNotification, object: nil)
        
        
        return true
}

func orientationChanged(notification: NSNotification ){
        //println("Application received orentation change notification")
        UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
        let orientation : UIDeviceOrientation = UIDevice.currentDevice().orientation
        var num : NSNumber = NSNumber(integer: orientation.rawValue)
        let userDict : [String: NSNumber] = [ "orientation" : num ]
        NSNotificationCenter.defaultCenter().postNotificationName("orientationWillChange", object: self, userInfo: userDict  )
        UIDevice.currentDevice().endGeneratingDeviceOrientationNotifications()

}

 3.  Subscribe to notifications in the non rotating view controller.

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.tabBarController?.tabBar.hidden = true
        // Add observer to listen for device rotation
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name: "orientationWillChange", object: nil)
 }
Now to react to these we're going to rotate the subviews that need to be rotated.  This is done in 3 methods:
func orientationChanged(notification: NSNotification ){
        println("Recieved notification of orientation change")
        UIDevice.currentDevice().orientation
        if let info = notification.userInfo as? Dictionary<String,NSNumber> {
            if let ori = info["orientation"] {
                println("orientation: \(ori)")
                let newOr : UIDeviceOrientation = UIDeviceOrientation(rawValue: ori.integerValue)!
                rotateSubviewsForOrientation(newOr)
            }
        }
    }
    
    func rotateSubviewsForOrientation(orientation: UIDeviceOrientation) {
        // rotate the subviews.
        switch orientation {
        case UIDeviceOrientation.LandscapeLeft:
            // home buitton facing right
            subLabelTransform(CGFloat(M_PI_2 ))
        case UIDeviceOrientation.LandscapeRight:
            // home button facing left
            subLabelTransform(CGFloat(3 * M_PI_2))
        default:
            subLabelTransform(CGFloat(0))
        }
    }
    
    func subLabelTransform(f: CGFloat) {
        UIView.animateWithDuration(0.2, animations: { () -> Void in
            //
            self.label1.transform = CGAffineTransformMakeRotation(f)
            self.label2.transform = CGAffineTransformMakeRotation(f)
            self.mainLabel.transform = CGAffineTransformMakeRotation(f)
            
        }) { (Bool) -> Void in
            //println("Done")
        }
        
        
    }
Maybe you have a better way?  I'd love to know! There is one problem with this method:  If the application launches in landscape mode then you'll have to rotate it a few times to actually work in the right mode. See the full code here.