SwiftUI interface orientation specific layouts

113 Views Asked by At

I have started experimenting with SwiftUI in order to port my app from UIKit to SwiftUI. Below is the code that is one of the building blocks. Basically, I want to place a 16:9 red subview in the center of the screen if current interface orientation is .landscape with a small offset to the left(offset dependent on safeAreaInsets). If the orientation is .portrait, then the red subview has aspect ratio 9:16 and is placed in the center with a small vertical offset (determined by safeAreaInsets). I have used device.orientation in the below code but I am looking for a better solution for a reason. Reason being that in UIKit, I could programatically turn on/off autorotation or fix orientation of view controller to a configured choice. I did not therefore rely on UIDevice orientation in UIKit. It doesn't seems to be straightforward in SwiftUI. I would not want to rely on device orientation therefore.

  var body: some View {
    Color.black
        .ignoresSafeArea()
        .overlay(alignment: .center,  content: {
            Color.red
                .aspectRatio(aspectRatio, contentMode: .fit)
                .ignoresSafeArea()
                .offset(x:previewHorizontalOffset, y:previewVerticalOffset)
            
        })
        .persistentSystemOverlays(.hidden)   
    }

   var aspectRatio:CGFloat {
       if !UIDevice.current.orientation.isLandscape {
          return 9.0/16.0
       } else {
          return 16.0/9.0
       }
   }

    var previewHorizontalOffset:CGFloat {
        if !UIDevice.current.orientation.isLandscape {
           return 0
        } else {
           return -15
        }
    }

    var previewVerticalOffset:CGFloat {
       if !UIDevice.current.orientation.isLandscape {
          return 15
       } else {
          return 0
       }
    }
1

There are 1 best solutions below

3
jnpdx On

You can use a GeometryReader to read the size of the View and decide whether or not it's landscape based on that size:

struct ContentView: View {
    var body: some View {
        GeometryReader { proxy in
            let isLandscape: Bool = proxy.size.width > proxy.size.height
            Color.black
                .ignoresSafeArea()
                .overlay(alignment: .center,  content: {
                    Color.red
                        .aspectRatio(aspectRatio(isLandscape: isLandscape), contentMode: .fit)
                        .ignoresSafeArea()
                        .offset(x:previewHorizontalOffset(isLandscape: isLandscape),
                                y:previewVerticalOffset(isLandscape: isLandscape))
                    
                })
                .persistentSystemOverlays(.hidden)
        }
    }
    
    func aspectRatio(isLandscape: Bool) -> CGFloat {
        if !isLandscape {
            return 9.0/16.0
        } else {
            return 16.0/9.0
        }
    }
    
    func previewHorizontalOffset(isLandscape: Bool) -> CGFloat {
        if !isLandscape {
            return 0
        } else {
            return -15
        }
    }
    
    func previewVerticalOffset(isLandscape: Bool) -> CGFloat {
        if !isLandscape {
            return 15
        } else {
            return 0
        }
    }
}

Note that GeometryReader greedily takes up space. If this affects your layout in unintended ways, you may want to use something like this: https://www.fivestars.blog/articles/swiftui-share-layout-information/