gpt4 book ai didi

ios - 在顶级 View 中居中 SwiftUI View

转载 作者:行者123 更新时间:2023-12-01 16:11:29 25 4
gpt4 key购买 nike

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

遗憾的是,我的应用程序并非完全基于 SwiftUI,因此我无法轻松地在我的 Root View 上设置属性,然后我可以在我的加载 View 中访问这些属性——我需要这个 View 居中,无论 View 层次结构是什么样的(混合 UIKit - SwiftUI 父 View )。这就是为什么像 SwiftUI set position to centre of different view 这样的答案不适用于我的用例,因为在该示例中,您需要修改要将 subview 居中的 View 。

我试过使用 View.offset.position 函数,但是,我无法获得正确的输入始终动态地居中我的 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)
}
}

应在其上方显示加载 View 的 View :

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)
}
}
}
}

结果是这样的: current (incorrect) UI

UI 应该是这样的: desired UI

我尝试使用 GeometryReader.frame(in: .global) 修改 CenteredLoadingViewbody > 以获得全局屏幕尺寸,但我所取得的成就是现在我的 loadingView 根本不可见。

var body: some View {
GeometryReader<AnyView> { geo in
let screen = geo.frame(in: .global)
let stack = ZStack {
self.rootView
self.loadingView
.position(x: screen.midX, y: screen.midY)
// Offset doesn't work either
//.offset(x: -screen.origin.x, y: -screen.origin.y)
}
// Ensure that `AnimatedLoadingView` is displayed above all other views, whose `zIndex` would be higher than `rootView`'s by default
.zIndex(.infinity)
return AnyView(stack)
}
}

最佳答案

这是一个可能的方法演示。这个想法是使用注入(inject)的 UIView 来访问 UIWindow,然后将加载 View 显示为窗口的 Root View Controller View 的顶 View 。

使用 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)
}
}
}
}

关于ios - 在顶级 View 中居中 SwiftUI View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63251367/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com