【问题标题】:SwiftUI - in sheet have a fixed continue button that is not scrollableSwiftUI - 在工作表中有一个不可滚动的固定继续按钮
【发布时间】:2020-12-11 12:40:09
【问题描述】:

正如您所见,即使我试图将工作表拉下,继续按钮也不会向下移动。我怎样才能让我的工作表表现得像那样?在我的应用程序中,继续按钮移出屏幕。这是我的应用程序在轻轻拉下工作表时的外观:

我还在下面附上了我的代码,它在横向和纵向上看起来都很美观。有没有办法在不破坏 iPhone 7 等小型设备的横向显示效果的情况下实现这一目标?

import SwiftUI

struct IntroView: View {
    @State private var animationAmount: CGFloat = 1
    @Environment(\.presentationMode) var presentationMode
    @Environment(\.verticalSizeClass) var sizeClass
    
    var body: some View {
        VStack {
            VStack {
                Spacer()
                if sizeClass == .compact {
                    HStack {
                        Text("Welcome to Demo").fontWeight(.heavy)
                        Text("App").foregroundColor(.orange).fontWeight(.heavy)
                    }
                    .padding(.bottom, 10)
                }
                
                else {
                    Text("Welcome to").fontWeight(.heavy)
                    HStack {
                        Text("Demo").fontWeight(.heavy)
                        Text("App").foregroundColor(.orange).fontWeight(.heavy)
                    }
                    .padding(.bottom, 30)
                }
            }//Intro VStack close
            .font(.largeTitle)
            .frame(maxWidth: .infinity, maxHeight: 180)
            
            VStack (spacing: 30) {
                HStack (spacing: 20) {
                    Image(systemName: "sparkle")
                        .foregroundColor(.yellow)
                        .font(.title2)
                        .scaleEffect(animationAmount)
                        .onAppear {
                            let baseAnimation = Animation.easeInOut(duration: 1)
                            let repeated = baseAnimation.repeatForever(autoreverses: true)
                            return withAnimation(repeated) {
                                self.animationAmount = 1.5
                            }
                        }
                    VStack (alignment: .leading) {
                        Text("All new design").fontWeight(.semibold)
                        Text("Easily view all your essentials here.")
                            .foregroundColor(.gray)
                    }
                    Spacer()
                }//HStack 1
                .padding([.leading, .trailing], 10)
                
                HStack (spacing: 20) {
                    Image(systemName: "pin")
                        .foregroundColor(.red)
                        .font(.title2)
                        .padding(.trailing, 5)
                        .scaleEffect(animationAmount)
                        .onAppear {
                            let baseAnimation = Animation.easeInOut(duration: 1)
                            let repeated = baseAnimation.repeatForever(autoreverses: true)
                            return withAnimation(repeated) {
                                self.animationAmount = 1.5
                            }
                        }
                    VStack (alignment: .leading) {
                        Text("Pin favourites").fontWeight(.semibold)
                        Text("You can pin your favourite content on all devices")
                            .foregroundColor(.gray)
                    }
                    Spacer()
                }//HStack 2
                .padding([.leading, .trailing], 10)
                
                .frame(maxWidth: .infinity, maxHeight: 100)
                
                HStack (spacing: 20) {
                    Image(systemName: "moon.stars.fill")
                        .foregroundColor(.blue)
                        .font(.title2)
                        .scaleEffect(animationAmount)
                        .onAppear {
                            let baseAnimation = Animation.easeInOut(duration: 1)
                            let repeated = baseAnimation.repeatForever(autoreverses: true)
                            return withAnimation(repeated) {
                                self.animationAmount = 1.5
                            }
                        }
                    VStack (alignment: .leading) {
                        Text("Flexible").fontWeight(.semibold)
                        Text("Supports dark mode")
                            .foregroundColor(.gray)
                    }
                    Spacer()
                }//HStack 3
                .padding([.leading, .trailing], 10)
                
            }//VStack for 3 criterias
            .padding([.leading, .trailing], 20)
            
                Spacer()
            
            Button {
                presentationMode.wrappedValue.dismiss()
                UserDefaults.standard.set(true, forKey: "LaunchedBefore")
            } label: {
                Text("Continue")
                    .fontWeight(.medium)
                    .padding([.top, .bottom], 15)
                    .padding([.leading, .trailing], 90)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(15)
            }
            .frame(maxWidth: .infinity, maxHeight: 100)

        }//Main VStack
    }
}
struct IntroView_Previews: PreviewProvider {
    static var previews: some View {
        IntroView()
    }
}

【问题讨论】:

  • P.S:在这种情况下使用 ScrollView 不起作用

标签: swift animation layout swiftui


【解决方案1】:

这是一个可能方法的演示(调整和效果超出范围 - 尽量使演示代码简短)。这个想法是用 above 工作表的按钮注入UIView 持有人,这样它在工作表向下拖动期间仍然存在(因为发现显示任何动态偏移都会产生一些难看的不受欢迎的摇晃效果)。

使用 Xcode 12 / iOS 14 测试

            // ... your above code here

            }//VStack for 3 criterias
            .padding([.leading, .trailing], 20)

                Spacer()

             // button moved from here into below background view !!

        }.background(BottomView(presentation: presentationMode) {
            Button {
                presentationMode.wrappedValue.dismiss()
                UserDefaults.standard.set(true, forKey: "LaunchedBefore")
            } label: {
                Text("Continue")
                    .fontWeight(.medium)
                    .padding([.top, .bottom], 15)
                    .padding([.leading, .trailing], 90)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(15)
            }
        })
        //Main VStack
    }
}

struct BottomView<Content: View>: UIViewRepresentable {
    @Binding var presentationMode: PresentationMode
    private var content: () -> Content

    init(presentation: Binding<PresentationMode>, @ViewBuilder _ content: @escaping () -> Content) {
        _presentationMode = presentation
        self.content = content
    }

    func makeUIView(context: Context) -> UIView {
        let view = UIView()

        DispatchQueue.main.async {
            if let window = view.window {
                let holder = UIView()
                context.coordinator.holder = holder

                // simple demo background to make it visible
                holder.layer.backgroundColor = UIColor.gray.withAlphaComponent(0.5).cgColor

                holder.translatesAutoresizingMaskIntoConstraints = false

                window.addSubview(holder)
                holder.heightAnchor.constraint(equalToConstant: 140).isActive = true
                holder.bottomAnchor.constraint(equalTo: window.bottomAnchor, constant: 0).isActive = true
                holder.leadingAnchor.constraint(equalTo: window.leadingAnchor, constant: 0).isActive = true
                holder.trailingAnchor.constraint(equalTo: window.trailingAnchor, constant: 0).isActive = true

                if let contentView = UIHostingController(rootView: content()).view {
                    contentView.backgroundColor = UIColor.clear
                    contentView.translatesAutoresizingMaskIntoConstraints = false
                    holder.addSubview(contentView)

                    contentView.topAnchor.constraint(equalTo: holder.topAnchor, constant: 0).isActive = true
                    contentView.bottomAnchor.constraint(equalTo: holder.bottomAnchor, constant: 0).isActive = true
                    contentView.leadingAnchor.constraint(equalTo: holder.leadingAnchor, constant: 0).isActive = true
                    contentView.trailingAnchor.constraint(equalTo: holder.trailingAnchor, constant: 0).isActive = true
                }
            }
        }
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        if !presentationMode.isPresented {
            context.coordinator.holder.removeFromSuperview()
        }
    }

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

    class Coordinator {
        var holder: UIView!

        deinit {
            holder.removeFromSuperview()
        }
    }
}

【讨论】:

  • 这是否意味着在 pure SwiftUI 中不能用另一个视图覆盖整个屏幕(包括工作表)?
  • @pawello2222,现在 - 是的。所有 SwiftUI 视图堆栈都呈现到根托管视图控制器 UIView 中,但工作表显示在单独的 UIView 中,因此要对其进行处理,我们首先需要创建显式 UIView。
【解决方案2】:

只需添加:

.sheet(isPresented: self.$visibleSheet) {
    IntroView(visibleSheet: self.$visibleSheet)
        .presentation(shouldDismissOnDrag: false)
}

https://stackoverflow.com/a/61239704/7974174

extension View {
   func presentation(shouldDismissOnDrag: Bool, onDismissalAttempt: (()->())? = nil) -> some View {
       ModalView(view: self, shouldDismiss: shouldDismissOnDrag, onDismissalAttempt: onDismissalAttempt)
   }
}

struct ModalView<T: View>: UIViewControllerRepresentable {
   let view: T
   let shouldDismiss: Bool
   let onDismissalAttempt: (()->())?
   
   func makeUIViewController(context: Context) -> UIHostingController<T> {
       UIHostingController(rootView: view)
   }
   
   func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {
       uiViewController.parent?.presentationController?.delegate = context.coordinator
   }
   
   func makeCoordinator() -> Coordinator {
       Coordinator(self)
   }
   
   class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
       let modalView: ModalView
       
       init(_ modalView: ModalView) {
           self.modalView = modalView
       }
       
       func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
           modalView.shouldDismiss
       }
       
       func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
           modalView.onDismissalAttempt?()
       }
   }
}

它通过向下拖动工作表来禁用工作表关闭。如果您想使用按钮关闭工作表,请不要再使用presentationMode。传递 self.$visibleSheet 的绑定,然后从内部修改为 false...

【讨论】:

    猜你喜欢
    • 2021-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多