在顶层视图中居中SwiftUI视图

时间:2020-08-04 16:38:54

标签: ios swift swiftui

我正在SwiftUI中创建一个加载指示器,该指示器应始终位于视图层次结构的顶级视图的中心(即在全屏应用程序的整个屏幕中居中)。在UIKit中这很容易,但是SwiftUI仅使视图相对于其父视图居中,而我无法获取父视图的父视图位置。

不幸的是,我的应用程序并非完全基于SwiftUI,因此我无法轻松地在根视图中设置可以在加载视图中访问的属性-无论视图层次结构是什么样,我都需要将此视图居中(混合UIKit -SwiftUI父视图)。这就是为什么SwiftUI set position to centre of different view之类的答案不适用于我的用例的原因,因为在该示例中,您需要修改要将子视图居中的视图。

我尝试使用.offset的{​​{1}}和.position函数,但是无论如何,我无法获得正确的输入来始终动态居中View屏幕大小或loadingView占据整个屏幕的哪个部分。

请在下面找到该问题的最小重现示例:

rootView

在其上方应显示加载视图的视图:

/// Loading view that should always be centered in the whole screen on the XY axis and should be the top view in the Z axis
struct CenteredLoadingView<RootView: View>: View {
    private let rootView: RootView

    init(rootView: RootView) {
        self.rootView = rootView
    }

    var body: some View {
        ZStack {
            rootView
                loadingView
        }
             // Ensure that `AnimatedLoadingView` is displayed above all other views, whose `zIndex` would be higher than `rootView`'s by default
            .zIndex(.infinity)
    }

    private var loadingView: some View {
        VStack {
            Color.white
                .frame(width: 48, height: 72)
            Text("Loading")
                .foregroundColor(.white)
        }
            .frame(width: 142, height: 142)
            .background(Color.primary.opacity(0.7))
            .cornerRadius(10)
    }
}

结果如下所示: current (incorrect) UI

这是UI的外观: desired UI

我尝试使用struct CenterView: View { var body: some View { return VStack { Color.gray HStack { CenteredLoadingView(rootView: list) otherList } } } var list: some View { List { ForEach(1..<6) { Text($0.description) } } } var otherList: some View { List { ForEach(6..<11) { Text($0.description) } } } } body来修改CenteredLoadingView的{​​{1}},以获得全局屏幕尺寸,但是我现在实现的是{ {1}}完全看不见。

GeometryReader

1 个答案:

答案 0 :(得分:1)

这里是可能方法的演示。想法是使用注入的UIView访问UIWindow,然后将加载视图显示为窗口根视图控制器视图的顶视图。

在Xcode 12 / iOS 14上进行了测试(但与SwiftUI 1.0兼容)

enter image description here

注意:动画,效果等是可能的,但超出了简单性的范围

struct CenteredLoadingView<RootView: View>: View {
    private let rootView: RootView
    @Binding var isActive: Bool

    init(rootView: RootView, isActive: Binding<Bool>) {
        self.rootView = rootView
        self._isActive = isActive
    }

    var body: some View {
        rootView
            .background(Activator(showLoading: $isActive))
    }

    struct Activator: UIViewRepresentable {
        @Binding var showLoading: Bool
        @State private var myWindow: UIWindow? = nil

        func makeUIView(context: Context) -> UIView {
            let view = UIView()
            DispatchQueue.main.async {
                self.myWindow = view.window
            }
            return view
        }

        func updateUIView(_ uiView: UIView, context: Context) {
            guard let holder = myWindow?.rootViewController?.view else { return }

            if showLoading && context.coordinator.controller == nil {
                context.coordinator.controller = UIHostingController(rootView: loadingView)

                let view = context.coordinator.controller!.view
                view?.backgroundColor = UIColor.black.withAlphaComponent(0.8)
                view?.translatesAutoresizingMaskIntoConstraints = false
                holder.addSubview(view!)
                holder.isUserInteractionEnabled = false

                view?.leadingAnchor.constraint(equalTo: holder.leadingAnchor).isActive = true
                view?.trailingAnchor.constraint(equalTo: holder.trailingAnchor).isActive = true
                view?.topAnchor.constraint(equalTo: holder.topAnchor).isActive = true
                view?.bottomAnchor.constraint(equalTo: holder.bottomAnchor).isActive = true
            } else if !showLoading {
                context.coordinator.controller?.view.removeFromSuperview()
                context.coordinator.controller = nil
                holder.isUserInteractionEnabled = true
            }
        }

        func makeCoordinator() -> Coordinator {
            Coordinator()
        }

        class Coordinator {
            var controller: UIViewController? = nil
        }

        private var loadingView: some View {
            VStack {
                Color.white
                    .frame(width: 48, height: 72)
                Text("Loading")
                    .foregroundColor(.white)
            }
                .frame(width: 142, height: 142)
                .background(Color.primary.opacity(0.7))
                .cornerRadius(10)
        }
    }
}

struct CenterView: View {
    @State private var isLoading = false
    var body: some View {
        return VStack {
            Color.gray
            HStack {
                CenteredLoadingView(rootView: list, isActive: $isLoading)
                otherList
            }
            Button("Demo", action: load)
        }
        .onAppear(perform: load)
    }

    func load() {
        self.isLoading = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.isLoading = false
        }
    }

    var list: some View {
        List {
            ForEach(1..<6) {
                Text($0.description)
            }
        }
    }

    var otherList: some View {
        List {
            ForEach(6..<11) {
                Text($0.description)
            }
        }
    }
}