我正在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)
}
}
我尝试使用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
答案 0 :(得分:1)
这里是可能方法的演示。想法是使用注入的UIView访问UIWindow,然后将加载视图显示为窗口根视图控制器视图的顶视图。
在Xcode 12 / iOS 14上进行了测试(但与SwiftUI 1.0兼容)
注意:动画,效果等是可能的,但超出了简单性的范围
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)
}
}
}
}