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

Aug 22 2020

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

3 Asperi Aug 29 2020 at 20:38

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 UIViewsupporto 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()
        }
    }
}
1 LorisFoe Aug 28 2020 at 20:07

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 presentationModepiù. Passa un'associazione di self.$visibleSheetquindi modifica a falso dall'interno ...