SwiftUI: nel foglio è presente un pulsante Continua fisso che non è scorrevole

Come puoi vedere, anche se sto provando a tirare giù il foglio, il pulsante continua non si sposta verso il basso. Come posso fare in modo che il mio foglio si comporti in questo modo? Nella mia app il pulsante Continua si sposta fuori dallo schermo. Ecco come appare la mia app quando il foglio viene abbassato leggermente:

Ho anche allegato il mio codice di seguito, sembra estetico sia sull'orientamento orizzontale che verticale. C'è un modo per farcela senza rovinare l'aspetto in orizzontale su dispositivi più piccoli come l'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()
}
}
Risposte
Ecco una demo del possibile approccio (la messa a punto e gli effetti sono fuori portata - prova a rendere breve il codice demo). L'idea è di iniettare il UIView
supporto con il pulsante sopra il foglio in modo che persista durante il trascinamento del foglio verso il basso (perché, come mostrato dai risultati, qualsiasi offset dinamico dà alcuni brutti effetti indesiderati di scuotimento).
Testato con 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()
}
}
}
Aggiungilo semplicemente:
.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?()
}
}
}
Disabilita la chiusura del foglio trascinando il foglio verso il basso. Se vuoi chiudere il foglio con il pulsante non usarlo presentationMode
più. Passa un'associazione di self.$visibleSheet
quindi modifica a falso dall'interno ...