How to rotate views in-place when orientation changes?

I would like to keep my layout the same, and only rotate views in-place when the screen orientation changes.


Below is a simple example of what I am trying to acieve. It shows to TestViews horizontally when in portrait mode, and vertically when in landscape mode.


I've made sure that the views themselve does not get re-created in the hope that it would only animate the difference in layout between portrait and landscape, but it makes the views very small, and then animate them in from the corner of the screen.


What I am trying to acieve, is for each view to effectively rotate by 90 degrees when the orientation changes. Is it possible in swiftUI?


[In my example below, I've hacked my AppDelegate to be my model to get rotation notifications from a publisher in it. This needs to be added for the example to work.]


struct TestView: View {
  var text: String
  
  var body: some View {
    ZStack {
      RoundedRectangle(cornerRadius: 25.0, style: .continuous)
        .foregroundColor(Color(red: 0.2, green: 0.2, blue: 0.2))
      Text(verbatim: text)
        .font(.system(size: 95))
        .foregroundColor(.white)
    }.aspectRatio(1.0, contentMode: .fit)
  }
}

struct ContentView: View {
  @EnvironmentObject var model : AppDelegate
  
  private let testViews: [TestView] = (0...1).map { TestView(text: "\($0)") }
  
  var body: some View {
    ZStack {
      if !model.isPotrait {
        HStack {
          VStack {
            testViews[0]
            testViews[1]
          }
        }
      } else {
        VStack {
          HStack {
            testViews[0]
            testViews[1]
          }
        }
      }
    }.padding()
  }
}
Answered by oreman in 420877022

Hi OOPer,


Below is my quickly hacked code to handle rotations. You could use any model that publishes isPortrait to use it as the environmental object.


class AppDelegate: UIResponder, ObservableObject, UIApplicationDelegate {

  @Published var isPotrait = true

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
    
    return true
  }
  
  @objc func rotated() {
    self.isPotrait = UIDevice.current.orientation.isPortrait
    print("isPortrait = \(isPotrait)")
  }

  func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
  }

  func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
  }
}

You also need to add the following (or something similar) to the SceneDelegate where the ContentView is instantiated:

// Create the SwiftUI view that provides the window contents.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let contentView = ContentView().environmentObject(appDelegate)

This needs to be added for the example to work.

Please show the code.

Accepted Answer

Hi OOPer,


Below is my quickly hacked code to handle rotations. You could use any model that publishes isPortrait to use it as the environmental object.


class AppDelegate: UIResponder, ObservableObject, UIApplicationDelegate {

  @Published var isPotrait = true

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
    
    return true
  }
  
  @objc func rotated() {
    self.isPotrait = UIDevice.current.orientation.isPortrait
    print("isPortrait = \(isPotrait)")
  }

  func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
  }

  func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
  }
}

You also need to add the following (or something similar) to the SceneDelegate where the ContentView is instantiated:

// Create the SwiftUI view that provides the window contents.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let contentView = ContentView().environmentObject(appDelegate)

Normally, it's not recommended to detect device orientation, rather UI, instead.

Thanks for showing your code. I will try a few things using your code. I will report when I find something to tell.

Hello all,

I'm wanting to code something similar.  Right now I have a List - which does a vertical scroll just naturally.  I assumed (what an ) that I'd just add a modifier to make List(.horizontal) but the compiler rejected that real fast.  That resulted in about 6 google searches (one found this thread) with no easy results.  Next (should have been first) went to Mark Moeykens Swift UI Views Mastery book (awesome resource) - quickly found that I needed to sub out the List for a ScrollView that can do HORIZONTAL!!  But my first Purview/Canvas test proved it was not just a drop in replacement for List.  It compiled but the views I have in the list display with differently colored white space.

So now that I've a view that can handle up&down as well as side-to-side... I need the env to tell my view which one it should use.

I'm confused by the Apple docs... (imagine that Apple - - take some of the nest egg and hire some Tech-Writers for the developers you make so much money with!)

what the heck is this trying very hard to tell me?? 


UIInterfaceOrientation

Starting in iOS 8, you should employ the UITraitCollection and UITraitEnvironmentAPIs, and size class properties as used in those APIs, instead of using UIInterfaceOrientation constants or otherwise writing your app in terms of interface orientation.

Important -in a nice ugly yellow box-

Notice that UIDeviceOrientation.landscapeRight is assigned to UIInterfaceOrientation.landscapeLeft and UIDeviceOrientation.landscapeLeft is assigned to UIInterfaceOrientation.landscapeRight. The reason for this is that rotating the device requires rotating the content in the opposite direction.

This UIInterfaceOrientation.isPortrait seems like the call I'd want to shove into the ScrollView
  ...

  ScrollView( UIInterfaceOrientation.isPortrait ? .horizontal : .vertical ) { ... }



I'm trying to decipher the  notes ... about versions ... is it supported ?  is it supported in 14+ and beyond?  Or am I that can not understand the docs English?


How to rotate views in-place when orientation changes?
 
 
Q