最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

mobile - Multiplatform SwiftUI app with a next layout: a top bar, bottom tab bar and a viewport in the middle - Stack Overflow

programmeradmin1浏览0评论

I'd like to create my pet project on SwiftUI for iPhone, iPad and MacOs (Mac Catalyst). My idea to have the next layout on all devices: A top bar (with buttons) above, a bottom tab bar at the bottom, and a Viewport view in the middle, taking all remaining area.

I also would like to get the size of the Viewport and save it in a global variable (ObservableObject): But every time I have some issue on different devices (sometimes the Viewport covers all screen or overlapping with a top bar or with a tab bar.

class GlobalUI: ObservableObject {
  @Published var viewportSize: CGSize = .zero // Store available viewport size
}


struct ContentView: View {
@EnvironmentObject var globalUI: GlobalUI
@State var selectedTab: TabSelection = .first

var body: some View {
    GeometryReader { geometry in
        VStack(spacing: 0) {
            // Top Bar
            CustomTopBar(selectedTab: selectedTab)
                .ignoresSafeArea(edges: .top)
            
            // Main Viewport
            Viewport(selectedTab: selectedTab)
                .frame(height: geometry.size.height - 100)
            
            // Bottom Tab Bar
            CustomTabBar(selectedTab: $selectedTab)
                .frame(height: 50)
                .padding(.bottom, getSafeAreaBottomInset())
                .background(Color("BrandGreen"))
                .ignoresSafeArea(edges: .bottom)
        }
    }
    .background(Color.black)
    .edgesIgnoringSafeArea(.all)
}
}

// another attempt:

struct ContentView: View {
@EnvironmentObject var globalUI: GlobalUI
@State var selectedTab: TabSelection = .first

func getSafeAreaBottomInset() -> CGFloat {
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
        return 0
    }
    return windowScene.windows.first?.safeAreaInsets.bottom ?? 0
}

var body: some View {
    VStack(spacing: 0) {
        CustomTopBar(selectedTab: selectedTab)
            .expandViewOutOfSafeArea(.top)
        
        GeometryReader { geometry in
            VStack(spacing: 0) {
                Viewport(selectedTab: selectedTab)
                    .onAppear {
                        let newSize = geometry.size
                        if newSize.width > 0 && newSize.height > 0 {
                            globalUI.viewportSize = newSize
                            print("viewport size updated: \(newSize)")
                        }
                    }
                    .onChange(of: geometry.size) { newSize in
                        if newSize.width > 0 && newSize.height > 0 {
                            globalUI.viewportSize = newSize
                            print("viewport size updated: \(newSize)")
                        }
                    }
                    .frame(maxWidth: .infinity, maxHeight: geometry.size.height - getSafeAreaBottomInset() - 100)  // Adjust height to exclude CustomTabBar height

                Spacer()
            }
        }
        
        CustomTabBar(selectedTab: $selectedTab)
            .frame(height: 50)
            .padding(.bottom, getSafeAreaBottomInset())
            .background(Color("BrandGreen")) // Ensures a solid background
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(Color.black)
    .ignoresSafeArea(edges: [.horizontal, .bottom])
}
}

I'd like to create my pet project on SwiftUI for iPhone, iPad and MacOs (Mac Catalyst). My idea to have the next layout on all devices: A top bar (with buttons) above, a bottom tab bar at the bottom, and a Viewport view in the middle, taking all remaining area.

I also would like to get the size of the Viewport and save it in a global variable (ObservableObject): But every time I have some issue on different devices (sometimes the Viewport covers all screen or overlapping with a top bar or with a tab bar.

class GlobalUI: ObservableObject {
  @Published var viewportSize: CGSize = .zero // Store available viewport size
}


struct ContentView: View {
@EnvironmentObject var globalUI: GlobalUI
@State var selectedTab: TabSelection = .first

var body: some View {
    GeometryReader { geometry in
        VStack(spacing: 0) {
            // Top Bar
            CustomTopBar(selectedTab: selectedTab)
                .ignoresSafeArea(edges: .top)
            
            // Main Viewport
            Viewport(selectedTab: selectedTab)
                .frame(height: geometry.size.height - 100)
            
            // Bottom Tab Bar
            CustomTabBar(selectedTab: $selectedTab)
                .frame(height: 50)
                .padding(.bottom, getSafeAreaBottomInset())
                .background(Color("BrandGreen"))
                .ignoresSafeArea(edges: .bottom)
        }
    }
    .background(Color.black)
    .edgesIgnoringSafeArea(.all)
}
}

// another attempt:

struct ContentView: View {
@EnvironmentObject var globalUI: GlobalUI
@State var selectedTab: TabSelection = .first

func getSafeAreaBottomInset() -> CGFloat {
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
        return 0
    }
    return windowScene.windows.first?.safeAreaInsets.bottom ?? 0
}

var body: some View {
    VStack(spacing: 0) {
        CustomTopBar(selectedTab: selectedTab)
            .expandViewOutOfSafeArea(.top)
        
        GeometryReader { geometry in
            VStack(spacing: 0) {
                Viewport(selectedTab: selectedTab)
                    .onAppear {
                        let newSize = geometry.size
                        if newSize.width > 0 && newSize.height > 0 {
                            globalUI.viewportSize = newSize
                            print("viewport size updated: \(newSize)")
                        }
                    }
                    .onChange(of: geometry.size) { newSize in
                        if newSize.width > 0 && newSize.height > 0 {
                            globalUI.viewportSize = newSize
                            print("viewport size updated: \(newSize)")
                        }
                    }
                    .frame(maxWidth: .infinity, maxHeight: geometry.size.height - getSafeAreaBottomInset() - 100)  // Adjust height to exclude CustomTabBar height

                Spacer()
            }
        }
        
        CustomTabBar(selectedTab: $selectedTab)
            .frame(height: 50)
            .padding(.bottom, getSafeAreaBottomInset())
            .background(Color("BrandGreen")) // Ensures a solid background
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(Color.black)
    .ignoresSafeArea(edges: [.horizontal, .bottom])
}
}
Share Improve this question asked Mar 3 at 14:08 Alex PiluginAlex Pilugin 7052 gold badges11 silver badges39 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

Instead of forcing the Viewport to adopt the size you give it, just let it expand to use all the space available. This is done by applying a frame with maxWidth: .infinity, maxHeight: .infinity.

If you need to know the size of the viewport, use .onGeometryChange to read it. A GeometryReader is not needed.

Btw, when you apply a background color using background(_:ignoresSafeAreaEdges:), the safe area insets are ignored by default. So there is no need to add a modifier to ignore them explicitly.

struct ContentView: View {
    @EnvironmentObject var globalUI: GlobalUI
    @State var selectedTab: TabSelection = .first

    var body: some View {
        VStack(spacing: 0) {
            // Top Bar
            CustomTopBar(selectedTab: selectedTab)
                .ignoresSafeArea(edges: .top)

            // Main Viewport
            Viewport(selectedTab: selectedTab)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
                .onGeometryChange(for: CGSize.self) { proxy in
                    proxy.size
                } action: { size in
                    globalUI.viewportSize = size
                }

            // Bottom Tab Bar
            CustomTabBar(selectedTab: $selectedTab)
                .frame(height: 50)
                .background(Color("BrandGreen"))
        }
        .background(.black)
    }
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论