1

I created a simple tabView like this

struct ContentView: View {
    var body: some View {
        TabView{
            TabItemView(color: .red, title: "Page 1")
            TabItemView(color: .yellow, title: "Page 2")
            TabItemView(color: .green, title: "Page 3")
            TabItemView(color: .blue, title: "Page 4")
        }
        .tabViewStyle(.page)
        .background(Color.indigo)
    }
}

where

struct TabItemView: View {
    var color : Color
    var title : String
    var body: some View {
        Text("\(title)")
            .frame(width:UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            .background(color)
        
    }
}

issue is when is switch to landscape entire thing is broken , like thisLandscape View

ie the tabs are jumped back to either some random position or position between two.

I searched online and some resources say its a bug thats not solved yet . Any solution for this?

Ae Ri
  • 185
  • 1
  • 12

1 Answers1

1

Hi the bug is well known. Generally it helps when you put the tabview into inactive scrollview with certain frame modifier, like this:

struct ContentView: View {
    var body: some View {
        ScrollView([])
            TabView{
                TabItemView(color: .red, title: "Page 1")
                TabItemView(color: .yellow, title: "Page 2")
                TabItemView(color: .green, title: "Page 3")
                TabItemView(color: .blue, title: "Page 4")
            }
            .tabViewStyle(.page)
            .background(Color.indigo)
        }
    }
    .frame(width: UIScreen.main.bounds.width)
}

This should fix the views in the middle in case of rotations. However, it might happen that when you use some array and iterate over it using ForEach inside the TabView, the elements are arbitrarily changed by TabView while rotating view. In that case it helps to keep tracking the current and previous orientation using states and build some logic onChange to prevent TabView to doing that. Like some wrapper adding layer between the binded state. Like:

struct TabView: View {
    @State var pages: Int = 1
    var array: [Int] = [1,2,3,4,5]

    var body: some View {
        VStack {
            TabViewContent(selection: $pages, usage: .one) {
                ForEach(array, id: \.self) { index in
                    Text("This is: \(pages) \(index)")
                    .tag(index)
                }
            }
        }
    }
}

Wrapper with logic:

struct TabViewContent<Selection: Hashable, Content: View>: View {
    @Binding var selection: Selection
    @State var selectionInternal: Selection

    @State var previousOrientation: UIDeviceOrientation = .unknown
    @State var currentOrientation: UIDeviceOrientation = .unknown

    @ViewBuilder var content: () -> Content

    internal init(selection: Binding<Selection>,     content: @escaping () -> Content) {
        self.content = content
        _selection = selection
        _selectionInternal = State(initialValue: selection.wrappedValue)
    }

    var body: some View {
        TabView(selection: $selection, content: content)
            .tabViewStyle(.page)
            .onChange(of: UIDevice.current.orientation) { newOrientation in
                currentOrientation = newOrientation
            }
            .onChange(of: selectionInternal) { value in
                if currentOrientation == previousOrientation {
                    selection = selectionInternal
                }

                previousOrientation = currentOrientation
            }
    }
}