SwiftUI Repaint View Components on Device Rotation

后端 未结 12 1874
谎友^
谎友^ 2020-12-01 01:00

How to detect device rotation in SwiftUI and re-draw view components?

I have a @State variable initialized to the value of UIScreen.main.bounds.width when the first

12条回答
  •  时光取名叫无心
    2020-12-01 01:49

    I wanted to know if there is simple solution within SwiftUI that works with any enclosed view so it can determine a different landscape/portrait layout. As briefly mentioned by @dfd GeometryReader can be used to trigger an update.

    Note that this works in the special occasions where use of the standard size class/traits do not provide sufficient information to implement a design. For example, where a different layout is required for portrait and landscape but where both orientations result in a standard size class being returned from the environment. This happens with the largest devices, like the max sized phones and with iPads.

    This is the 'naive' version and this does not work.

    struct RotatingWrapper: View {
         
          var body: some View {
                GeometryReader { geometry in
                    if geometry.size.width > geometry.size.height {
                         LandscapeView()
                     }
                     else {
                         PortraitView()
                    }
               }
         }
    }
    

    This following version is a variation on a rotatable class that is a good example of function builders from @reuschj but just simplified for my application requirements https://github.com/reuschj/RotatableStack/blob/master/Sources/RotatableStack/RotatableStack.swift

    This does work

    struct RotatingWrapper: View {
        
        func getIsLandscape(geometry:GeometryProxy) -> Bool {
            return geometry.size.width > geometry.size.height
        }
        
        var body: some View {
            GeometryReader { geometry in
                if self.getIsLandscape(geometry:geometry) {
                    Text("Landscape")
                }
                else {
                    Text("Portrait").rotationEffect(Angle(degrees:90))
                }
            }
        } 
    }
    

    That is interesting because I'm assuming that some SwiftUI magic has caused this apparently simple semantic change to activate the view re-rendering.

    One more weird trick that you can use this for, is to 'hack' a re-render this way, throw away the result of using the GeometryProxy and perform a Device orientation lookup. This then enables use of the full range of orientations, in this example the detail is ignored and the result used to trigger a simple portrait and landscape selection or whatever else is required.

    enum  Orientation {
        case landscape 
        case portrait 
    }
    
    struct RotatingWrapper: View {
       
        func getOrientation(geometry:GeometryProxy) -> Orientation {
            let _  = geometry.size.width > geometry.size.height
            if   UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft || UIDevice.current.orientation == UIDeviceOrientation.landscapeRight {
                return .landscape
            }
            else {
                return .portrait
            }
         }
        
        var body: some View {
           ZStack {
            GeometryReader { geometry in
                if  self.getOrientation(geometry: geometry) == .landscape {
                     LandscapeView()
                 }
                 else {
                     PortraitView()
                }
            }
            }
         }
        
    }
    

    Furthermore, once your top level view is being refreshed you can then use DeviceOrientation directly, such as the following in child views as all child views will be checked once the top level view is 'invalidated'

    Eg: In the LandscapeView() we can format child views appropriately for its horizontal position.

    struct LandscapeView: View {
        
        var body: some View {
             HStack   {
                Group {
                if  UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft {
                    VerticallyCenteredContentView()
                }
                    Image("rubric")
                        .resizable()
                                  .frame(width:18, height:89)
                                  //.border(Color.yellow)
                        .padding([UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft ? .trailing : .leading], 16)
                }
                if  UIDevice.current.orientation == UIDeviceOrientation.landscapeRight {
                  VerticallyCenteredContentView()
                }
             }.border(Color.pink)
       }
    }
    

提交回复
热议问题