How to detect orientation change?

2019-01-02 14:46发布

问题:

I am using Swift and I want to be able to load a UIViewController when I rotate to landscape, can anyone point me in the right direction?

I Can't find anything online and a little bit confused by the documentation.

回答1:

Here's how I got it working:

In AppDelegate.swift inside the didFinishLaunchingWithOptions function I put:

NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

and then inside the AppDelegate class I put the following function:

func rotated() {
    if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
        print("Landscape")
    }

    if UIDeviceOrientationIsPortrait(UIDevice.current.orientation) {
        print("Portrait")
    }

}

Hope this helps anyone else!

Thanks!



回答2:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}


回答3:

Need to detect rotation while using the camera with AVFoundation, and found that the didRotate (now deprecated) & willTransition methods were unreliable for my needs. Using the notification posted by David did work, but is not current for Swift 3.x/4.x.

Swift 4.2 The notification name has been changed.

The closure value remains the same as Swift 4.0:

var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other")
        }
    }

To set up the notification for Swift 4.2:

NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification,
                                       object: nil,
                                       queue: .main,
                                       using: didRotate)

To tear down the notification for Swift 4.2:

NotificationCenter.default.removeObserver(self,
                                          name: UIDevice.orientationDidChangeNotification,
                                          object: nil)

Regarding the deprecation statement, my initial comment was misleading, so I wanted to update that. As noted, the usage of @objc inference has been deprecated, which in turn was needed to use a #selector. By using a closure instead, this can be avoided and you now have a solution that should avoid a crash due to calling an invalid selector.

Everything below here is obsolete as of XCode 10 & iOS 4.2

Swift 4.0 With Swift 4.0, Apple has encouraged us to avoid using the #selector, so this approach uses a completion block now. This approach is also backwards compatible with Swift 3.x & would be the recommended approach going forward.

This is the compiler warning you will receive in a Swift 4.x project if you use the #selector function due to the deprecation of @objc inference:

Entry in swift-evolution on this change.

Setup the callback:

// If you do not use the notification var in your callback, 
// you can safely replace it with _
    var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other")
        }
    }

Setup the notification:

NotificationCenter.default.addObserver(forName: .UIDeviceOrientationDidChange,
                                       object: nil,
                                       queue: .main,
                                       using: didRotate)

Tear it down:

NotificationCenter.default.removeObserver(self, name: .UIDeviceOrientationDidChange, object: nil)


回答4:

Using -orientation property of UIDevice is not correct (even if it could work in most of cases) and could lead to some bugs, for instance UIDeviceOrientation consider also the orientation of the device if it is face up or down, there is no direct pair in UIInterfaceOrientation enum for those values.
Furthermore, if you lock your app in some particular orientation, UIDevice will give you the device orientation without taking that into account.
On the other side iOS8 has deprecated the interfaceOrientation property on UIViewController class.
There are 2 options available to detect the interface orientation:

  • Use the status bar orientation
  • Use size classes, on iPhone if they are not overridden they could give you a way to understand the current interface orientation

What is still missing is a way to understand the direction of a change of interface orientation, that is very important during animations.
In the session of WWDC 2014 "View controller advancement in iOS8" the speaker provides a solution to that problem too, using the method that replaces -will/DidRotateToInterfaceOrientation.

Here the proposed solution partially implemented, more info here:

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        let orientation = orientationFromTransform(coordinator.targetTransform())
        let oldOrientation = UIApplication.sharedApplication().statusBarOrientation
        myWillRotateToInterfaceOrientation(orientation,duration: duration)
        coordinator.animateAlongsideTransition({ (ctx) in
            self.myWillAnimateRotationToInterfaceOrientation(orientation,
            duration:duration)
            }) { (ctx) in
                self.myDidAnimateFromInterfaceOrientation(oldOrientation)
        }
    }


回答5:

Easy, this works in iOS8 and 9 / Swift 2 / Xcode7, just put this code inside your viewcontroller.swift. It will print the screen dimensions with every orientation change, you can put your own code instead:

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
        getScreenSize()
    }
    var screenWidth:CGFloat=0
    var screenHeight:CGFloat=0
    func getScreenSize(){
        screenWidth=UIScreen.mainScreen().bounds.width
        screenHeight=UIScreen.mainScreen().bounds.height
        print("SCREEN RESOLUTION: "+screenWidth.description+" x "+screenHeight.description)
    }


回答6:

Use the new viewWillTransitionToSize(_:withTransitionCoordinator:)



回答7:

I know this question is for Swift, but since it's one of the top links for a Google search and if you're looking for the same code in Objective-C:

// add the observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rotated:) name:UIDeviceOrientationDidChangeNotification object:nil];

// remove the observer
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];

// method signature
- (void)rotated:(NSNotification *)notification {
    // do stuff here
}


回答8:

In Objective C

-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

In swift

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

Override this method to detect the orientation change.



回答9:

Swift 3 | UIDeviceOrientationDidChange Notification Observed Too Often

The following code prints "deviceDidRotate" every time your device changes orientation in 3D space - regardless of a change from portrait to landscape orientation. For example, if you hold your phone in portrait orientation and tilt it forward and backward - deviceDidRotate() is called repeatedly.

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}

To work around this you could hold the previous device orientation and check for a change in deviceDidRotate().

var previousDeviceOrientation: UIDeviceOrientation = UIDevice.current.orientation

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    if UIDevice.current.orientation == previousDeviceOrientation { return }
    previousDeviceOrientation = UIDevice.current.orientation
    print("deviceDidRotate")
}

Or you can use a different notification that only gets called when the device changes from landscape to portrait. In this case you'd want to use the UIApplicationDidChangeStatusBarOrientation notification.

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIApplicationDidChangeStatusBarOrientation, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}


回答10:

Full working implementation of how to detect orientation change in Swift 3.0.

I chose to use this implementation because phone orientations of face up and face down were important to me, and I wanted the view to change only once I knew the orientation was in the specified position.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //1
        NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

    }

    deinit {
        //3
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    func deviceOrientationDidChange() {
        //2
        switch UIDevice.current.orientation {
        case .faceDown:
            print("Face down")
        case .faceUp:
            print("Face up")
        case .unknown:
            print("Unknown")
        case .landscapeLeft:
            print("Landscape left")
        case .landscapeRight:
            print("Landscape right")
        case .portrait:
            print("Portrait")
        case .portraitUpsideDown:
            print("Portrait upside down")
        }
    }

}

The important pieces to note are:

  1. You listen to the DeviceOrientationDidChange notification stream and tie it to the function deviceOrientationDidChange
  2. You then switch on the device orientation, be sure to take notice that there is an unknown orientation at times.
  3. Like any notification, before the viewController is deinitialized, make sure to stop observing the notification stream.

Hope someone finds this helpful.



回答11:

override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
    //swift 3
    getScreenSize()
}


func getScreenSize(){
   let screenWidth = UIScreen.main.bounds.width
   let  screenHeight = UIScreen.main.bounds.height
    print("SCREEN RESOLUTION: \(screenWidth.description) x \(screenHeight.description)")
}


回答12:

Check if rotation had changed with: viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

With the coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext) you can check if the transition is finished.

See code below:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {

    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext) in
        // if you want to execute code after transition finished
        print("Transition finished")
    }

    if size.height < size.width {
        // Landscape
        print("Landscape")
    } else {
        // Portrait
        print("Portrait")
    }

}


回答13:

Here is an easy way to detect the device orientation: (Swift 3)

override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
            handleViewRotaion(orientation: toInterfaceOrientation)
        }

    //MARK: - Rotation controls
    func handleViewRotaion(orientation:UIInterfaceOrientation) -> Void {
        switch orientation {
        case .portrait :
            print("portrait view")
            break
        case .portraitUpsideDown :
            print("portraitUpsideDown view")
            break
        case .landscapeLeft :
            print("landscapeLeft view")
            break
        case .landscapeRight :
            print("landscapeRight view")
            break
        case .unknown :
            break
        }
    }


回答14:

I like checking the orientation notification because you can add this feature in any class, no needs to be a view or a view controller. Even in your app delegate.

    //ask the system to start notifying when interface change
    UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
    //add the observer
    NSNotificationCenter.defaultCenter().addObserver(
        self,
        selector: #selector(self.orientationChanged(_:)),
        name: UIDeviceOrientationDidChangeNotification,
        object: nil)

than caching the notification

    func orientationChanged(notification : NSNotification) {
        //your code there
    }


回答15:

My approach is similar to what bpedit shows above, but with an iOS 9+ focus. I wanted to change the scope of the FSCalendar when the view rotates.

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.setScope(.Week, animated: true)
            self.calendar.appearance.cellShape = .Rectangle
        }
        else {
            self.calendar.appearance.cellShape = .Circle
            self.calendar.setScope(.Month, animated: true)

        }

        }, completion: nil)
}

This below worked, but I felt sheepish about it :)

coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.scope = .Week
            self.calendar.appearance.cellShape = .Rectangle
        }
        }) { (context) in
            if size.height > size.width {
                self.calendar.scope = .Month
                self.calendar.appearance.cellShape = .Circle
            }
    }


回答16:

For Swift 3

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        //Landscape
    }
    else {
        //Portrait
    }
}


回答17:

I use UIUserInterfaceSizeClass to detect a orientation changed in a UIViewController class just like that:

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {

    let isiPadLandscapePortrait = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .regular
    let isiPhonePlustLandscape = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .compact
    let isiPhonePortrait = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .regular
    let isiPhoneLandscape = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .compact

     if isiPhonePortrait {
         // do something...
     }
}


回答18:

If you want to do something AFTER the rotation is complete, you can use the UIViewControllerTransitionCoordinator completion handler like this

public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    // Hook in to the rotation animation completion handler
    coordinator.animate(alongsideTransition: nil) { (_) in
        // Updates to your UI...
        self.tableView.reloadData()
    }
}


回答19:

Since iOS 8 this is the correct way to do it.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    coordinator.animate(alongsideTransition: { context in
        // This is called during the animation
    }, completion: { context in
        // This is called after the rotation is finished. Equal to deprecated `didRotate`
    })
}


回答20:

- (void)viewDidLoad {
  [super viewDidLoad];
  [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}

-(void)OrientationDidChange:(NSNotification*)notification {
  UIDeviceOrientation Orientation=[[UIDevice currentDevice]orientation];

  if(Orientation==UIDeviceOrientationLandscapeLeft || Orientation==UIDeviceOrientationLandscapeRight) {
    NSLog(@"Landscape");
  } else if(Orientation==UIDeviceOrientationPortrait) {
    NSLog(@"Potrait Mode");
  }
}

NOTE: Just use this code to identify UIViewController is in which orientation



标签: