Change sizes after change orientation - ios

func SetDeviceFontSizes()
{
...
imgView.frame.size.width = 375
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
SetDeviceFontSizes()
}
override func viewWillLayoutSubviews() {
SetDeviceFontSizes()
}
override func viewDidLoad() {
super.viewDidLoad()
SetDeviceFontSizes()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
SetDeviceFontSizes()
}
I want to set sizes for labels and images (for old devices these sizes will be small).
But when I change orientation of device, the sizes return to default values in spite of function SetDeviceFontSizes().
How can I set frame sizes after change orientation?

var m_CurrentOrientation = UIDeviceOrientation()
add this line in your viewdidload()
UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.deviceOrientationDidChange), name: .DidChangeNotification, object: nil)
perform your task in if condition
func OrientationDidChange(notification: NSNotification) {
var Orientation = UIDevice.currentDevice().orientation
if Orientation == .LandscapeLeft || Orientation == .LandscapeRight {
}
else if Orientation == .Portrait {
}
}

i'm not sure this will solve your problem, but you should call super in viewWillLayoutSubviews and viewWillTransition

You can use UITraitCollection. Refer to this link
How to translate this objective c traitCollection to swift?
Do not call anything in viewWillLayoutSubviews() function.
Hope it helps you.

Related

Unexpected behavior when the constraints are applied for the first time

I want to change the layout and constraints relative to the orientation of the device.
Here's how I implemented it:
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
addViews()
constraintSubviews()
}
func constraintSubviews() {
if UIDevice.current.orientation.isPortrait {
NSLayoutConstraint.activate(portraitConstrains)
NSLayoutConstraint.deactivate(landscapeConstrains)
NSLayoutConstraint.activate(nowPlayingView.portraitConstrains)
NSLayoutConstraint.deactivate(nowPlayingView.landscapeConstrains)
} else {
NSLayoutConstraint.deactivate(portraitConstrains)
NSLayoutConstraint.activate(landscapeConstrains)
NSLayoutConstraint.activate(nowPlayingView.landscapeConstrains)
NSLayoutConstraint.deactivate(nowPlayingView.portraitConstrains)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
constraintSubviews()
}
Here's what I ended up when running the code:
And this is what I expect:
I can't get the layout I designed in the code unless I change device orientation once.
Better implementation
I've searched all over the Internet and the best thing I found was this implementation. Is there a better implementation regarding this subject?
You need
1- Make deactivate before activate
func constraintSubviews() {
if UIDevice.current.orientation.isPortrait {
NSLayoutConstraint.deactivate(landscapeConstrains)
NSLayoutConstraint.deactivate(nowPlayingView.landscapeConstrains)
NSLayoutConstraint.activate(nowPlayingView.portraitConstrains)
NSLayoutConstraint.activate(portraitConstrains)
} else {
NSLayoutConstraint.deactivate(portraitConstrains)
NSLayoutConstraint.deactivate(nowPlayingView.portraitConstrains)
NSLayoutConstraint.activate(landscapeConstrains)
NSLayoutConstraint.activate(nowPlayingView.landscapeConstrains)
}
}
2- Replace
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
constraintSubviews()
}
with
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: size, with: coordinator)
constraintSubviews()
}

How to maintain landscape or portrait mode when converting app to IPAD?

I'm currently adapting an iphone app to ipad and have implemented the following code in the viewController to maintain one of the views in landscape (the other views are in portrait):
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
//TODO: - Keep presentationView in landscape
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
override func viewDidDisappear(_ animated: Bool) {
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeLeft
}
override var shouldAutorotate: Bool {
return true
}
This works fine for the iphone but does not work when displaying the app on iPad i.e. the view rotates and can be seen in both portrait and landscape. Be grateful for any suggestions on how I can adapt the code to make it work on iPad.
Add method viewWillTransition(to:with:).
Notifies the container that the size of its view is about to change.UIKit calls this method before changing the size of a presented view controller’s view. You can override this method in your own objects and use it to perform additional tasks related to the size change.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
print("Landscape")
// Update frame
} else {
print("Portrait")
// Update frame
}
}

Swift - How to detect orientation changes

I want to add two images to single image view (i.e for landscape one image and for portrait another image)but i don't know how to detect orientation changes using swift languages.
I tried this answer but it takes only one image
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.currentDevice().orientation.isLandscape.boolValue {
print("Landscape")
} else {
print("Portrait")
}
}
I am new to iOS development,any advice would be greatly appreciated!
let const = "Background" //image name
let const2 = "GreyBackground" // image name
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(named: const)
// Do any additional setup after loading the view.
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
print("Landscape")
imageView.image = UIImage(named: const2)
} else {
print("Portrait")
imageView.image = UIImage(named: const)
}
}
Using NotificationCenter and UIDevice's beginGeneratingDeviceOrientationNotifications
Swift 4.2+
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}
func rotated() {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
}
Swift 3
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func rotated() {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
}
⚠️Device Orientation != Interface Orientation⚠️
Swift 5.* iOS16 and below
You should really make a difference between:
Device Orientation => Indicates the orientation of the physical device
Interface Orientation => Indicates the orientation of the interface displayed on the screen
There are many scenarios where those 2 values are mismatching such as:
When you lock your screen orientation
When you have your device flat
In most cases you would want to use the interface orientation and you can get it via the window:
private var windowInterfaceOrientation: UIInterfaceOrientation? {
return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
}
In case you also want to support < iOS 13 (such as iOS 12) you would do the following:
private var windowInterfaceOrientation: UIInterfaceOrientation? {
if #available(iOS 13.0, *) {
return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
} else {
return UIApplication.shared.statusBarOrientation
}
}
Now you need to define where to react to the window interface orientation change. There are multiple ways to do that but the optimal solution is to do it within
willTransition(to newCollection: UITraitCollection.
This inherited UIViewController method which can be overridden will be trigger every time the interface orientation will be change. Consequently you can do all your modifications in the latter.
Here is a solution example:
class ViewController: UIViewController {
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }
if windowInterfaceOrientation.isLandscape {
// activate landscape changes
} else {
// activate portrait changes
}
})
}
private var windowInterfaceOrientation: UIInterfaceOrientation? {
return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
}
}
By implementing this method you'll then be able to react to any change of orientation to your interface. But keep in mind that it won't be triggered at the opening of the app so you will also have to manually update your interface in viewWillAppear().
I've created a sample project which underlines the difference between device orientation and interface orientation. Additionally it will help you to understand the different behavior depending on which lifecycle step you decide to update your UI.
Feel free to clone and run the following repository:
https://github.com/wjosset/ReactToOrientation
Swift 3
Above code updated:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
}
Swift 4+:
I was using this for soft keyboard design, and for some reason the UIDevice.current.orientation.isLandscape method kept telling me it was Portrait, so here's what I used instead:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if(size.width > self.view.frame.size.width){
//Landscape
}
else{
//Portrait
}
}
If your are using Swift version >= 3.0 there are some code updates you have to apply as others have already said. Just don't forget to call super:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// YOUR CODE OR FUNCTIONS CALL HERE
}
If you are thinking to use a StackView for your images be aware you can do something like the following:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if UIDevice.current.orientation.isLandscape {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
} // else
}
If your are using Interface Builder don't forget to select the custom class for this UIStackView object, in the Identity Inspector section at the right panel. Then just create (also through Interface Builder) the IBOutlet reference to the custom UIStackView instance:
#IBOutlet weak var stackView: MyStackView!
Take the idea and adapt it to your needs. Hope this can help you!
Swift 4.2, RxSwift
If we need to reload collectionView.
NotificationCenter.default.rx.notification(UIDevice.orientationDidChangeNotification)
.observeOn(MainScheduler.instance)
.map { _ in }
.bind(to: collectionView.rx.reloadData)
.disposed(by: bag)
Swift 4, RxSwift
If we need to reload collectionView.
NotificationCenter.default.rx.notification(NSNotification.Name.UIDeviceOrientationDidChange)
.observeOn(MainScheduler.instance)
.map { _ in }
.bind(to: collectionView.rx.reloadData)
.disposed(by: bag)
Here is a modern Combine solution:
import UIKit
import Combine
class MyClass: UIViewController {
private var subscriptions = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter
.default
.publisher(for: UIDevice.orientationDidChangeNotification)
.sink { [weak self] _ in
let orientation = UIDevice.current.orientation
print("Landscape: \(orientation.isLandscape)")
}
.store(in: &subscriptions)
}
}
I believe the correct answer is actually a combination of both approaches: viewWIllTransition(toSize:) and NotificationCenter's UIDeviceOrientationDidChange.
viewWillTransition(toSize:) notifies you before the transition.
NotificationCenter UIDeviceOrientationDidChange notifies you after.
You have to be very careful. For example, in UISplitViewController when the device rotates into certain orientations, the DetailViewController gets popped off the UISplitViewController's viewcontrollers array, and pushed onto the master's UINavigationController. If you go searching for the detail view controller before the rotation has finished, it may not exist and crash.
Swift 4
I've had some minor issues when updating the ViewControllers view using UIDevice.current.orientation, such as updating constraints of tableview cells during rotation or animation of subviews.
Instead of the above methods I am currently comparing the transition size to the view controllers view size. This seems like the proper way to go since one has access to both at this point in code:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
print("Will Transition to size \(size) from super view size \(self.view.frame.size)")
if (size.width > self.view.frame.size.width) {
print("Landscape")
} else {
print("Portrait")
}
if (size.width != self.view.frame.size.width) {
// Reload TableView to update cell's constraints.
// Ensuring no dequeued cells have old constraints.
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Output on a iPhone 6:
Will Transition to size (667.0, 375.0) from super view size (375.0, 667.0)
Will Transition to size (375.0, 667.0) from super view size (667.0, 375.0)
You can use viewWillTransition(to:with:) and tap into animate(alongsideTransition:completion:) to get the interface orientation AFTER the transition is complete. You just have to define and implement a protocol similar to this in order to tap into the event. Note that this code was used for a SpriteKit game and your specific implementation may differ.
protocol CanReceiveTransitionEvents {
func viewWillTransition(to size: CGSize)
func interfaceOrientationChanged(to orientation: UIInterfaceOrientation)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard
let skView = self.view as? SKView,
let canReceiveRotationEvents = skView.scene as? CanReceiveTransitionEvents else { return }
coordinator.animate(alongsideTransition: nil) { _ in
if let interfaceOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation {
canReceiveRotationEvents.interfaceOrientationChanged(to: interfaceOrientation)
}
}
canReceiveRotationEvents.viewWillTransition(to: size)
}
You can set breakpoints in these functions and observe that interfaceOrientationChanged(to orientation: UIInterfaceOrientation) is always called after viewWillTransition(to size: CGSize) with the updated orientation.
All previous contributes are fine, but a little note:
a) if orientation is set in plist, only portrait or example, You will be not notified via viewWillTransition
b) if we anyway need to know if user has rotated device, (for example a game or similar..) we can only use:
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
tested on Xcode8, iOS11
To get the correct orientation on app start you have to check it in viewDidLayoutSubviews(). Other methods described here won't work.
Here's an example how to do it:
var mFirstStart = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if (mFirstStart) {
mFirstStart = false
detectOrientation()
}
}
func detectOrientation() {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
// do your stuff here for landscape
} else {
print("Portrait")
// do your stuff here for portrait
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
detectOrientation()
}
This will work always, on app first start, and if rotating while the app is running.
Another way to detect device orientations is with the function traitCollectionDidChange(_:). The system calls this method when the iOS interface environment changes.
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
super.traitCollectionDidChange(previousTraitCollection)
//...
}
Furthermore, you can use function willTransition(to:with:) ( which is called before traitCollectionDidChange(_:) ), to get information just before the orientation is applied.
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
super.willTransition(to: newCollection, with: coordinator)
//...
}
My app is running on iOS 15 and I have checked only on iPhone/iPad so I can't say about all use cases however I am using the following environment variable:
#Environment(\.verticalSizeClass) private var verticalSizeClass
Then checking its value using the following:
verticalSizeClass == .compact is horizontal
verticalSizeClass == .regular is vertical
https://developer.apple.com/documentation/swiftui/environmentvalues/verticalsizeclass

Device Rotation Notification is not working as expected iOS Swift

func shouldirotate(){
var whichwaywhere: String {
if UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeLeft {
return "left"
}
if UIDevice.currentDevice().orientation == UIDeviceOrientation.LandscapeRight {
return "right"
}
if UIDevice.currentDevice().orientation == UIDeviceOrientation.PortraitUpsideDown {
return "down"
}
return "I don't Care"
}
println(whichwaywhere)
}
when I create a function that continuously checks itself, (attaching shouldirotate to a NSTimer) I can get the orientation checked. How could I tell the function to run when UIDeviceOrientationDidChangeNotification activates?
To get UIDeviceOrientationDidChangeNotification running I have a variable:
var rotatenote: Bool = UIDevice.currentDevice().generatesDeviceOrientationNotifications
and in the ViewDidLoad override I have:
override func viewDidLoad() {
UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
rotatenote = true
}
Is this the correct way to use and declare this property? how can I get the function to run off of DeviceOrientationNotification?
P.S.: I have rotatenote = true in the ViewDidLoad because if I try to attach it to the variable declaration like usual, it says "cannot assign to the result of this expression." See Below
var rotatenote: Bool = UIDevice.currentDevice().generatesDeviceOrientationNotifications = true
There are several ways to do this. Not sure what OS you are worried about, but in iOS 8 you've got two easy options. You can either register an observer for
UIDeviceOrientationDidChangeNotification
or you can override
func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
You should not need
UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
So either in viewDidLoad add:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "functionThatYouWantTriggeredOnRotation", name:
UIDeviceOrientationDidChangeNotification, object: nil)
Where you have a function functionThatYouWantTriggeredOnRotation that does the calculation...
Alternatively you can just provide the override for viewWillTransition and not have to add an observer for the UIDeviceOrientationDidChangeNotification
override func viewWillTransitionToSize(size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
if UIDevice.currentDevice().orientation.isLandscape {
//do your thing
}
}

willRotateToInterfaceOrientation deprecated, but don't understand viewWillTransitionToSize:withTransitionCoordinator:

I'm porting my iPad app to iOS8 and Swift.
In portrait, i use a root UIViewController and when the de device is rotated to landscape, I segue to another UIViewController. I've come up with 2 solutions, one based on UIDevice notifications and the other on willRotateToInterfaceRotation. I alway try to stay away from the Observer pattern, it's just a habit of me.
The Observer works fine, but the override in both UIViewController of
func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval)
looks cleaner to my eyes ;)
But now in iOS8 that function is deprecated and I should use
func viewWillTransitionToSize(_ size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
But I don't have any idea how I can work with that to get the same result.
This is the rootViewController:
override func willRotateToInterfaceOrientation(
toInterfaceOrientation: UIInterfaceOrientation,
duration: NSTimeInterval) {
if (toInterfaceOrientation == UIInterfaceOrientation.LandscapeLeft ||
toInterfaceOrientation == UIInterfaceOrientation.LandscapeRight) {
self.performSegueWithIdentifier("toLandscape", sender: self)
}
}
and the UIViewControllerLanscape:
override func willRotateToInterfaceOrientation(
toInterfaceOrientation: UIInterfaceOrientation,
duration: NSTimeInterval) {
if (toInterfaceOrientation == UIInterfaceOrientation.Portrait ||
toInterfaceOrientation == UIInterfaceOrientation.PortraitUpsideDown) {
self.presentingViewController?.dismissViewControllerAnimated(true,
completion: nil)
}
}
I don't like to use deprecated functions, so I'm in doubt what to do...go for the observer or what??
This is the code I use when (only) the root UIViewController is an observer of the UIDeviceOrientationDidChangeNotification:
override func awakeFromNib() {
let dev = UIDevice.currentDevice()
dev.beginGeneratingDeviceOrientationNotifications()
let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: "orientationChanged:", name: UIDeviceOrientationDidChangeNotification, object: dev)
}
func orientationChanged(note: NSNotification) -> () {
if let uidevice: UIDevice = note.object? as? UIDevice {
switch uidevice.orientation {
case .LandscapeLeft, .LandscapeRight:
self.performSegueWithIdentifier("toLandscape", sender: self)
default:
self.dismissViewControllerAnimated(true, completion: nil)
}
}
}
I really like your ideas on how to make a solution for these deprecated functions. It's a total mystery to me....or maybe the Observer is the better solution now. Please share your ideas.
Regards,
jr00n

Resources