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.
Related
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)
}
}
}
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 {
// ...
}
}
}
}
In this sample app, I have a title in the top left of the screen, and a button in the bottom right. I'm using stacks and spacers to align them.
Currently, when you press the button, it animates up/left a little. But I want the button to animate to the exact center of the screen (or safe area), regardless of device or button size. The code is shown below, along with images of the start and end of the animation I want.
struct ContentView: View {
#State var buttonIsMoved = false
var body: some View {
VStack {
HStack {
Text("Title")
.font(.largeTitle)
Spacer()
}
Spacer()
HStack {
Spacer()
// This is the button I want to animate to the center
Button(action: {
self.buttonIsMoved.toggle()
}) {
Text("This is a button")
.foregroundColor(.black)
.padding(16)
.background(Color.green)
}
// Currently I'm just using fixed offset values,
// but I want it to move to the actual center of the screen
.offset(buttonIsMoved ? CGSize(width: -50, height: -200) : .zero)
.animation(.easeInOut)
}
}
.padding(32)
}
}
Start of animation
End of animation
If I use .offset(), I don't know how to calculate the distance between the button's center and the center of the screen. I've also tried to use .position() but it's based on the parent view, which in this case is an HStack below the title, so it wouldn't be centered within the whole screen. I've also heard of GeometryReader, but I can't figure out how to use it for this purpose.
Here is possible solution - no hardcoding, based on SwiftUI native layout engine.
Tested with Xcode 11.4 / iOS 13.4
struct DemoAnimateLayout: View {
#State var buttonIsMoved = false
var body: some View {
ZStack {
VStack {
HStack {
Text("Title")
.font(.largeTitle)
Spacer()
}
Spacer()
}
VStack {
if !buttonIsMoved { // << here !!
Spacer()
}
HStack {
if !buttonIsMoved { // << here !!
Spacer()
}
// This is the button I want to animate to the center
Button(action: {
self.buttonIsMoved.toggle()
}) {
Text("This is a button")
.foregroundColor(.black)
.padding(16)
.background(Color.green)
}
}
}
.animation(.easeInOut) // << animate container layout !!
}.padding(32)
}
}
In my SwiftUI app, I would like to bring the navigation bar items down like in Apple's own UIKit apps.
Seen below is a screenshot from the Health app. Notice how the profile picture is in line with the 'Summary' text. This is what I am looking to achieve.
I have tried using .padding(.top, 90) but this has not worked as it does not bring down the virtual box that allows the button to be clicked. Using padding means that you have to tap the button above the image/text.
Thank you.
Unfortunately I didn't find any solution for changing navigation bar height in iOS 13 with SwiftUI, and had the same issues earlier. Solution below will fit you, if your navigation bar is always only black and you're ok with gap on the top:
struct NavBarCustomItems: View {
init() {
setNavigationBarToBlackOnly()
}
func setNavigationBarToBlackOnly() {
let blackAppearance = UINavigationBarAppearance()
blackAppearance.configureWithOpaqueBackground()
blackAppearance.backgroundColor = .black
blackAppearance.shadowColor = .clear // to avoid border line
UINavigationBar.appearance().standardAppearance = blackAppearance
UINavigationBar.appearance().scrollEdgeAppearance = blackAppearance
}
var body: some View {
NavigationView {
VStack {
NavigationBarMimicry()
// here is your content
HStack {
Text("Favorites")
Spacer()
Button(action: {}) { Text("Edit") }
}
.padding()
Spacer()
VStack {
Text("Main screen")
}
// you need spacer(s) to be sure, that NavigationBarMimicry is always on the top
Spacer()
}
}
}
}
// MARK: here is what you need in navigation bar
struct NavigationBarMimicry: View {
var body: some View {
HStack {
Text("Summary")
.bold()
.font(.system(size: 40))
.foregroundColor(.white)
.padding(.horizontal)
Spacer()
Rectangle()
.foregroundColor(.white)
.frame(width: 40)
.padding(.horizontal)
}
.background(Color.black)
.frame(height: 40)
.navigationBarTitle("", displayMode: .inline)
// you can add it to hide navigation bar, navigation will work via NavigationLink
// .navigationBarHidden(true)
}
}
struct NavBarCustomItems_Previews: PreviewProvider {
static var previews: some View {
NavBarCustomItems().environment(\.colorScheme, .dark)
}
}
the result should be like this:
P.S. maybe the other ways are:
Put views in this order: VStack { NavigationBarMimicry(); NavigationView {...}};
uncomment line of code: .navigationBarHidden(true);
I have a TabView with 2 tabs in it, each tab containing a NavigationView. I need to hide the TabBar when navigating to another view. One solution would be to place the TabView inside of one NavigationView, but I have to set different properties for each NavigationView.
TabView(selection: $selectedTab, content: {
NavigationView {
VStack {
NavigationLink(destination: Text("SecondView Tab1")) {
Text("Click")
}
}
}.tabItem {
Text("ONE")
}.tag(0)
NavigationView {
VStack {
NavigationLink(destination: Text("SecondView Tab2")) {
Text("Click")
}
}
}.tabItem {
Text("TWO")
}.tag(1)
})
P.S. I am using Xcode 11 Beta 5
A little late but it will work, put your NavigationView before the TabView and the tab buttons are going to be hidden when you use a navigation link in your tabbed views.
NavigationView{
TabView{
...
}
}
I have same problem for this;
And I did the following actions to solve this problem:
Use NavigationView Contain a TabView And Hidden the NavigationBar
Make a Custom NavigaitonView like this
In next view Still hidden NavigationBar
// root tab
NavigationView {
TabView {
// some
}
.navigationBarTitle(xxx, displayMode: .inline)
.navigationBarHidden(true)
}
// custom navigation view
#available(iOS 13.0.0, *)
struct MyNavigationView: View {
var body: some View {
HStack {
Spacer()
Text(some)
Spacer()
}
.frame(height: 44)
}
}
// this view
VStack {
MyNavigationView()
Image(some)
.resizable()
.frame(width: 100, height: 100, alignment: .top)
.padding(.top, 30)
Spacer()
HStack {
ClockView()
Spacer()
NavigationLink(
destination: DynamicList(),
label: {
Image(some)
}).navigationBarHidden(true)
}
.padding(EdgeInsets(top: 0, leading: 15, bottom: 0, trailing: 15))
Spacer()
}
// next view
var body: some View {
VStack {
List {
MyNavigationView()
ForEach(date, id: \.self) { model in
Text(model)
}
}
.navigationBarHidden(true)
.navigationBarTitle(some, displayMode: .inline)
}
}
You can't hide the tab bar as far as I know if you navigation view its listed as a child, your tab bar contains your navigation view.