Close a popover - ios

I have managed to show a popover in my app. It can be closed with a swipi down. Works great. However, it is adviced to also show a button to close the popover. I tried to add this to the code, with all the tips on pop-ons and pop-overs given on the site, but sofa I failed. Can someone help me out? Must be an easy command but for a starter in xcode/swift it ins't easy to find.
The code to show the popover and the code in the popover itself:
...
//The popover code in the ContentView that starts the popover
Button(action: {
presentPopup = true
}, label: {
Image(systemName: "questionmark.square")
.foregroundColor(Color.gray)
})
.padding(.leading)
.popover(isPresented: $presentPopup, arrowEdge: .bottom) {
Toelichting()
.font(.footnote)
}
//The code in the popover view:
var body: some View {
VStack {
HStack {
Text("Introduction")
.font(.title2)
.fontWeight(.bold)
.multilineTextAlignment(.leading)
.padding([.top, .leading])
//Spacer ()
// This should be the button to return to the main screen that ins't working
// Button (action: {
// self.dismiss(animated: true, completion: nil)
// }, label: {
// Image(systemName: "xmark.circle")
// .foregroundColor(Color.gray)
// })
// .padding([.top, .trailing])
}
Divider()
.padding(.horizontal)
.frame(height: 3.0)
.foregroundColor(Color.gray)
...
What should be Button action? Thanks for your time and help!

Rather than dismissing the popover using dismiss(), you need to pass a Binding value between both views.
In this way, the value of the Binding will either be true, triggered by the main view, or false, triggered inside the popover.
Here's your code:
struct TopView: View {
#State private var presentPopup = true // This controls the popover
var body: some View {
//The popover code in the ContentView that starts the popover
Button {
presentPopup = true
} label: {
Image(systemName: "questionmark.square")
.foregroundColor(Color.gray)
}
.padding(.leading)
.popover(isPresented: $presentPopup, arrowEdge: .bottom) {
// You need to pass the Binding to the popover
Toelichting(presentMe: $presentPopup)
.font(.footnote)
}
}
}
struct Toelichting: View {
#Binding var presentMe : Bool // This is how you trigger the dismissal
var body: some View {
VStack {
HStack {
Text("Introduction")
.font(.title2)
.fontWeight(.bold)
.multilineTextAlignment(.leading)
.padding([.top, .leading])
Spacer ()
// This should be the button to return to the main screen that NOW IT'S FINALLY working
Button (action: {
// Change the value of the Binding
presentMe.toggle()
}, label: {
Image(systemName: "xmark.circle")
.foregroundColor(Color.gray)
})
.padding([.top, .trailing])
}
Divider()
.padding(.horizontal)
.frame(height: 3.0)
.foregroundColor(Color.gray)
}
}
}

Related

Present a new view when a condition has been met in SwiftUI

Hi everyone I'm new to SwiftUI .. I need to present a view containing two textFields with modal presentation only after a condition has been checked.
Example .. When the user pushes the login button I need the app to check the database for the existence of the user. If the user exists he can access the app otherwise he must show a view where he must enter his name and surname.
With UIKit I used this to present a structure or class
self.present(userFound ? Home() : UserNameInfo(), animated: true, completion: nil)
but with SwiftUI I can't figure out how to solve this problem.
Can you help me in any way?
My code
var body: some View {
ZStack(alignment: .top) {
Color(.black).ignoresSafeArea()
gradientBackground().ignoresSafeArea()
VStack(alignment: .center, spacing: 25) {
Spacer()
logoStack()
// Messaggio di benvenuto
VStack(alignment: .leading, spacing: 15) {
Text("Effettua l'accesso al tuo account")
.font(.title2.bold())
Text("Riserva un momento esclusivo prenotando un taglio tradizionale oppure una rasatura con panni caldi e trattamenti viso")
.font(.callout.weight(.light))
}
.foregroundColor(.white)
//.dynamicTypeSize(.medium)
// Apple Sign In Button
SignInWithAppleView()
.frame(width: screen.size.width - 56)
.frame(height: 45, alignment: .center)
.onTapGesture(perform: showAppleLoginView)
// Divisore
Divider().background(.gray)
// Messaggio sull'accettazione della Privacy e delle Condizioni di utilizzo
VStack(spacing: 5) {
Text("Continuando dichiaro di accettare le ")
.multilineTextAlignment(.center)
HStack(spacing: 0) {
Button("Condizioni di Utilizzo") {}
.foregroundColor(.white)
.font(.footnote.bold())
Text(" ed i ")
Button("Termini sulla Privacy") {}
.foregroundColor(.white)
.font(.footnote.bold())
Text(".")
}
}
.foregroundColor(.gray)
.font(.footnote)
.padding(.bottom, 25)
}
.padding(.horizontal)
}
private func showAppleLoginView() {
// Show modalview if user not exist in Firebase
}
// MARK: - Apple Sign In Button View Representable
struct SignInWithAppleView: UIViewRepresentable {
typealias UIViewType = ASAuthorizationAppleIDButton
func makeUIView(context: Context) -> UIViewType {
ASAuthorizationAppleIDButton(type: .signIn, style: .white)
}
func updateUIView(_ uiView: UIViewType, context: Context) {}
}
You can use the modifier .fullScreenCover
& you just need to pass a binding to a #State var which you set to true when you want to display the modal.
example
struct ExampleScreenView: View {
#State var showModal: Bool = false
var body: some View {
VStack {
Text("Some Text")
.padding()
Button {
showModal = true
} label: {
Text("Show other view")
}
}
.fullScreenCover(isPresented: $showModal) {
VStack {
Color.white
Text("Some other text")
.padding()
Button {
showModal = false
} label: {
Text("close view")
}
}
}
}
}
This example the first view has a button that sets the bool to true, and shows the modal, the modal view has a button that sets the bool to false and closes the view.
The button is just for the sake of an example, but you can use any logic to set the bool. to true, and then you can present whatever view you choose within the fullScreenCover.
Specific for the login scenario, you can use a .onReceive modifier to listen for a notification sent from your login successful code.
.onReceive(NotificationCenter.default.publisher(for: Notification.Name(rawValue: "didLogin"))
&
NotificationCenter.post(name:Notification.Name("didLogin"), object: nil)

SwiftUI change view from first screen to tabview screen

I want to change views once the user taps 'get started' but due to having navigation view in my first view, it is showing back button on my next screen which I don't want. Please see the images attached below.
Code for the first view is below:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
NavigationLink(destination: ViewChanger()) {
Text("Get Started")
.font(.headline)
.navigationBarBackButtonHidden(true)
}
}
.padding()
}
}
}
back button showing on screen 2
First view
Change the location of your navigationBackButtonHidden modifier so that it actually modifies the view that you're going to (and not the NavigationLink label):
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
NavigationLink(destination: ViewChanger()
.navigationBarBackButtonHidden(true) // <-- Here
) {
Text("Get Started")
.font(.headline)
}
}
.padding()
}
}
}
If you want not only the back button to be gone, but the entire header bar, you can use the .navigationBarHidden(true) modifier.
Also, if you run this on iPad at all, you probably want .navigationViewStyle(StackNavigationViewStyle()) added to the outside of your NavigationView
If you use a NavigationLink (in a NavigationView), the view will be pushed. If you want to replace the view, you can do this with an if statement.
For example, this could be implemented like this:
struct ContentView: View {
#State var showSecondView: Bool = false
var body: some View {
if !showSecondView {
NavigationView {
VStack {
Spacer()
Text("LifePath")
.font(.system(size: 48, weight: .semibold))
.padding(.bottom)
Spacer()
Button(action: { showSecondView = true }) {
Text("Get Started")
.font(.headline)
}
}
.padding()
} else {
TabView {
// ...
}
}
}
}

SwiftUI navigation bar appearance and functionality when custom popup shows

I am trying to show a custom popup, and while that is showing, I'd need to disable and darken the background just as it is done in the built-in alert functionality. However, when there is a navigation bar in the view, the coloured layer can't be put on top of the navigation bar.
The desired outcome would be just as it is done in the built-in Alert modifier, to darken the whole background (with navbar), while disabling the ability to interact with the background (and the navbar).
Is there a way to achieve the same functionality and appearance as in with the built-in Alert modifier?
Example project code
import SwiftUI
struct ContentView: View {
#State private var isShowingPopup = false
var body: some View {
NavigationView {
VStack {
Text("Just a random text")
.padding(.bottom, 100)
Button("Show popup") {
isShowingPopup = true
}
}
.showPopup(isActive: isShowingPopup, action: { isShowingPopup = false })
.navigationBarTitle("Test navbar", displayMode: .inline)
.navigationBarItems(
trailing: Button(
action: { print("profile tapped")},
label: {
Text("Profile")
}
)
)
}
}
}
extension View {
func showPopup(
isActive: Bool,
action: #escaping () -> Void
) -> some View {
ZStack {
self
if isActive {
Color.black
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.opacity(0.51)
.zIndex(1)
Popup(action: action)
.zIndex(2)
}
}
}
}
struct Popup: View {
let action: () -> Void
var body: some View {
VStack {
Text("This is a popup")
.foregroundColor(.black)
.padding()
Button("OK", action: action)
.foregroundColor(.blue)
}
.background(Color.white)
.cornerRadius(8)
}
}
Thanks for your help!
Add in the last. Add showPopup in the last of the NavigationView
NavigationView {
VStack {
Text("Just a random text")
.padding(.bottom, 100)
Button("Show popup") {
isShowingPopup = true
}
}
.navigationBarTitle("Test navbar", displayMode: .inline)
.navigationBarItems(
trailing: Button(
action: { print("profile tapped")},
label: {
Text("Profile")
}
)
)
}
.showPopup(isActive: isShowingPopup, action: { isShowingPopup = false }) //<---Here

SwiftUI: How do you dismiss a sheet and launch a different sheet from ContentView?

For my app, I have a welcome screen that intro's what the app does and allows the user to create their first item. When the user clicks the button I'd like to dismiss the 'welcomeScreen' sheet and and then launch the 'newRemindr' sheet.
I tried to achieve this by creating an observable object with an 'addNewTrigger' boolean set to false. When I click the Add New Reminder button on the welcomeScreen, the button's action causes the welcomeScreen to dismiss and toggles the 'addNewTrigger' boolean to True. (I've verified this is working with Print Statements). However content view is listening to that same observed object to launch the 'newRemindr' sheet but that action doesn't seem to be working.
Can somebody please take a look at the code and see where I am going wrong? Or suggest an alternative that can provide the type of functionality.
I really appreciate all the help. Thanks!
Code Below...
welcomeScreen:
import SwiftUI
import Combine
struct welcomeScreen: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var addNewReminder = showAddScreen()
var body: some View {
NavigationView {
ZStack (alignment: .center) {
LinearGradient(gradient: Gradient(colors: [Color.white, Color.white, Color.gray]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
Image("Ellipse2")
.offset(y: -475)
VStack {
Spacer()
Text("Welcome to")
.foregroundColor(.white)
.fontWeight(.bold)
Image("RemindrLogoWhite")
Spacer()
Text("What is remindr?")
.font(.title)
.fontWeight(.bold)
.padding(.bottom, 25)
Text("Remindr is a simple app designed to help you schedule random reminders with the goal of clearing your mind.\n\nRemind yourself to check in with your body, set up positive affirmations, set your intentions; Whatever it is, the power is up to you.")
.padding(.horizontal, 25)
.padding(.bottom, 25)
Text("Click below to get started:")
.fontWeight(.bold)
// Add New Reminder Button
Button(action: {
self.mode.wrappedValue.dismiss()
print("Add Reminder Button from Welcome Screen is Tapped")
self.addNewReminder.addNewTrigger.toggle()
print("var addNewTrigger has been changed to \(self.addNewReminder.addNewTrigger)")
}) {
Image("addButton")
.renderingMode(.original)
}.padding(.bottom, 25)
Spacer()
} .frame(maxWidth: UIScreen.main.bounds.width,
maxHeight: UIScreen.main.bounds.height)
}
.navigationBarTitle(Text(""), displayMode: .automatic)
.navigationBarItems(trailing: Button(action: {
self.mode.wrappedValue.dismiss()
}, label: {
Image(systemName: "xmark")
.foregroundColor(.white)
}))
}
}
}
ContentView:
import SwiftUI
import CoreData
class showAddScreen: ObservableObject {
#Published var addNewTrigger = false
}
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: ReminderEntity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ReminderEntity.dateCreated, ascending: false)])
var reminder: FetchedResults<ReminderEntity>
// Sheet Control
#ObservedObject var addNewReminder = showAddScreen()
//#State private var showingAddScreen = false
#State var showWelcomeScreen = false
let emojiList = EmojiList()
//Toggle Control
#State var notifyOn = true
// Save Items Function
func saveItems() {
do {
try moc.save()
} catch {
print(error)
}
}
// Delete Item Function
func deleteItem(indexSet: IndexSet) {
let source = indexSet.first!
let listItem = reminder[source]
moc.delete(listItem)
}
// View Controller
var body: some View {
VStack {
NavigationView {
ZStack (alignment: .top) {
// List View
List {
ForEach(reminder, id: \.self) { notification in
NavigationLink(destination: editRemindr(reminder: notification,
notifyOn: notification.notifyOn,
emojiChoice: Int(notification.emojiChoice),
notification: notification.notification ?? "unknown",
notes: notification.notes ?? "unknown")) {
// Text within List View
HStack {
// MARK: TODO
//Toggle("NotifyOn", isOn: self.$notifyOn)
// .labelsHidden() // Hides the label/title
Text("\(self.emojiList.emojis[Int(notification.emojiChoice)]) \(notification.notification!)")
}
}
}
.onDelete(perform: deleteItem)
}.lineLimit(1)
// Navigation Items
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(
leading:
HStack {
Button(action: {
self.showWelcomeScreen.toggle()
}) {
Image(systemName: "info.circle.fill")
.font(.system(size: 24, weight: .regular))
}.foregroundColor(.gray)
// Positioning Remindr Logo on Navigation
Image("remindrLogoSmall")
.resizable()
.aspectRatio(contentMode: .fit)
//.frame(width: 60, height: 60, alignment: .center)
.padding(.leading, 83)
.padding(.top, -10)
},
// Global Settings Navigation Item
trailing: NavigationLink(destination: globalSettings()){
Image("settings")
.font(Font.title.weight(.ultraLight))
}.foregroundColor(.gray)
)
// Add New Reminder Button
VStack {
Spacer()
Button(action: { self.addNewReminder.addNewTrigger.toggle()
}) {
Image("addButton")
.renderingMode(.original)
}
.sheet(isPresented: $addNewReminder.addNewTrigger) {
newRemindr().environment(\.managedObjectContext, self.moc)
}
}
}
} .sheet(isPresented: $showWelcomeScreen) {
welcomeScreen()
}
}
}
}
First what I see is you use different observable objects in both views, but should use same, so changes made in one view be available for second view as well.
Se here is a way to solve this
struct welcomeScreen: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var addNewReminder: showAddScreen // << declare to be injected
// ... other code
and in ContentView
} .sheet(isPresented: $showWelcomeScreen) {
welcomeScreen(addNewReminder: self.addNewReminder) // << inject !!
}
Alternate: you can remove addNewReminder from welcomeScreen and work with it only in ContentView by activating on welcome sheet dismiss, like
} .sheet(isPresented: $showWelcomeScreen, onDismiss: {
// it is better to show second sheet with delay to give chance
// for first one to animate closing to the end
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.addNewReminder.addNewTrigger.toggle()
}
}
) {
welcomeScreen()
}

Present Modal fullscreem SwiftUI

how can i present a modal that will take up the fullscreen and can't be dismissed by swiping it down? Currently I am using .sheet on a view to present a modal that is dismissible.
I haven't noticed any beta changes in Xcode that changes this behavior.
Any help would be appreciated :)
SwiftUI 1.0
I'm not sure if this what you'd want to go with but it's possible to create your own modal screen by using the ZStack and a state variable to control the hiding/showing of it.
Code
struct CustomModalPopups: View {
#State private var showingModal = false
var body: some View {
ZStack {
VStack(spacing: 20) {
Text("Custom Popup").font(.largeTitle)
Text("Introduction").font(.title).foregroundColor(.gray)
Text("You can create your own modal popup with the use of a ZStack and a State variable.")
.frame(maxWidth: .infinity)
.padding().font(.title).layoutPriority(1)
.background(Color.orange).foregroundColor(Color.white)
Button(action: {
self.showingModal = true
}) {
Text("Show popup")
}
Spacer()
}
// The Custom Popup is on top of the screen
if $showingModal.wrappedValue {
// But it will not show unless this variable is true
ZStack {
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.vertical)
// This VStack is the popup
VStack(spacing: 20) {
Text("Popup")
.bold().padding()
.frame(maxWidth: .infinity)
.background(Color.orange)
.foregroundColor(Color.white)
Spacer()
Button(action: {
self.showingModal = false
}) {
Text("Close")
}.padding()
}
.frame(width: 300, height: 200)
.background(Color.white)
.cornerRadius(20).shadow(radius: 20)
}
}
}
}
}
Example
(Excerpt from "SwiftUI Views" book)
So here, your popup is small, but you can adjust the dimensions to make it fullscreen with the frame modifier that is on that VStack.

Resources