ios - 在顶级视图中居中 SwiftUI 视图
问题描述
我在 SwiftUI 中创建了一个加载指示器,它应该始终位于视图层次结构的顶层视图的中心(即在全屏应用程序的整个屏幕中居中)。这在 UIKit 中很容易,但是 SwiftUI 仅相对于其父视图将视图居中,我无法获取父视图的父视图的位置。
遗憾的是,我的应用程序并非完全基于 SwiftUI,因此我无法轻松地在我的根视图上设置属性,然后我可以在加载视图中访问这些属性 - 无论视图层次结构如何,我都需要此视图居中(混合 UIKit - SwiftUI 父级)意见)。这就是为什么SwiftUI set position to center of different view 之类的答案不适用于我的用例,因为在该示例中,您需要修改要使子视图居中的视图。
我尝试过使用.offset
和的.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
aGeometryReader
并.frame(in: .global)
获得全局屏幕尺寸,但我所取得的成就是现在 myloadingView
根本不可见。
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)
}
}
解决方案
这是一个可能的方法的演示。这个想法是使用注入的 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)
}
}
}
}
推荐阅读
- python - 比较相同数据的excel和文本文件的最佳方法
- regex - 用于捕获结构和联合的正则表达式
- ssh - 如何设置 ssh 客户端端口
- string - postgres中字符串的部分匹配和完全匹配
- c# - MongoDB C# CRUD
- sql-server - 裁剪 SQL Server Management Studio 窗口
- c# - C#输出自己的List数据类型
- instagram - 禁用旧 API 后,如何将我的 Instagram 提要添加到我的个人网站?
- python - kivy 滚动视图卡住了
- python-3.x - 消息:“geckodriver”可执行文件需要在 PATH 中