I would like to create a modal sheets with an image in the body, like in Human Interface Guidelines. Could you please give me an example?
https://developer.apple.com/design/human-interface-guidelines/watchos/interaction/modality/
The following code basically recreates the example screenshot from above:
import SwiftUI
struct ModalView: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
Image(systemName: "headphones")
.font(.system(size: 30))
Text("To play audio, connect Bluetooth headphones to your Apple Watch.")
.font(.footnote)
.multilineTextAlignment(.center)
}
.opacity(0.8)
.padding(10)
Spacer()
Button("Connect a Device") {
isPresented.toggle()
}.padding(.horizontal)
}
}
struct TestView: View {
#State private var isPresentingModalView = false
var body: some View {
Button("Connect") {
isPresentingModalView.toggle()
}
.fullScreenCover(isPresented: $isPresentingModalView) {
ModalView(isPresented: $isPresentingModalView)
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
Result:
See docs:
https://developer.apple.com/documentation/swiftui/view/fullscreencover(ispresented:ondismiss:content:)
Related
I am trying to push from login view to detail view but not able to make it.even navigation bar is not showing in login view. How to push on button click in SwiftUI? How to use NavigationLink on button click?
var body: some View {
NavigationView {
VStack(alignment: .leading) {
Text("Let's get you signed in.")
.bold()
.font(.system(size: 40))
.multilineTextAlignment(.leading)
.frame(width: 300, height: 100, alignment: .topLeading)
.padding(Edge.Set.bottom, 50)
Text("Email address:")
.font(.headline)
TextField("Email", text: $email)
.frame(height:44)
.accentColor(Color.white)
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
Text("Password:")
.font(.headline)
SecureField("Password", text: $password)
.frame(height:44)
.accentColor(Color.white)
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
Button(action: {
print("login tapped")
}) {
HStack {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
.accentColor(Color.black)
.padding()
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
.padding(Edge.Set.vertical, 20)
}
.padding(.horizontal,30)
}
.navigationBarTitle(Text("Login"))
}
To fix your issue you need to bind and manage tag with NavigationLink, So create one state inside you view as follow, just add above body.
#State var selection: Int? = nil
Then update your button code as follow to add NavigationLink
NavigationLink(destination: Text("Test"), tag: 1, selection: $selection) {
Button(action: {
print("login tapped")
self.selection = 1
}) {
HStack {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
.accentColor(Color.black)
.padding()
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
.padding(Edge.Set.vertical, 20)
}
Meaning is, when selection and NavigationLink tag value will match then navigation will be occurs.
I hope this will help you.
iOS 16+
Note: Below is a simplified example of how to present a new view. For a more advanced generic example please see this answer.
In iOS 16 we can access the NavigationStack and NavigationPath.
Usage #1
A new view is activated by a simple NavigationLink:
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink(value: "NewView") {
Text("Show NewView")
}
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
}
}
Usage #2
A new view is activated by a standard Button:
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Button {
path.append("NewView")
} label: {
Text("Show NewView")
}
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
}
}
Usage #3
A new view is activated programmatically:
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Text("Content View")
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
.onAppear {
path.append("NewView")
}
}
}
iOS 13+
The accepted answer uses NavigationLink(destination:tag:selection:) which is correct.
However, for a simple view with just one NavigationLink you can use a simpler variant: NavigationLink(destination:isActive:)
Usage #1
NavigationLink is activated by a standard Button:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
Button(action: {
self.isLinkActive = true
}) {
Text("Login")
}
}
}
.navigationBarTitle(Text("Login"))
}
}
}
Usage #2
NavigationLink is hidden and activated by a standard Button:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
Button(action: {
self.isLinkActive = true
}) {
Text("Login")
}
}
.navigationBarTitle(Text("Login"))
.background(
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
}
}
Usage #3
NavigationLink is hidden and activated programmatically:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
}
.navigationBarTitle(Text("Login"))
.background(
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
.onAppear {
self.isLinkActive = true
}
}
}
Here is a GitHub repository with different SwiftUI extensions that makes navigation easier.
Another approach:
SceneDelegate
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: BaseView().environmentObject(ViewRouter()))
self.window = window
window.makeKeyAndVisible()
}
BaseView
import SwiftUI
struct BaseView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "view1" {
FirstView()
} else if viewRouter.currentPage == "view2" {
SecondView()
.transition(.scale)
}
}
}
}
#if DEBUG
struct MotherView_Previews : PreviewProvider {
static var previews: some View {
BaseView().environmentObject(ViewRouter())
}
}
#endif
ViewRouter
import Foundation
import Combine
import SwiftUI
class ViewRouter: ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter,Never>()
var currentPage: String = "view1" {
didSet {
withAnimation() {
objectWillChange.send(self)
}
}
}
}
FirstView
import SwiftUI
struct FirstView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
Button(action: {self.viewRouter.currentPage = "view2"}) {
NextButtonContent()
}
}
}
}
#if DEBUG
struct FirstView_Previews : PreviewProvider {
static var previews: some View {
FirstView().environmentObject(ViewRouter())
}
}
#endif
struct NextButtonContent : View {
var body: some View {
return Text("Next")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.blue)
.cornerRadius(15)
.padding(.top, 50)
}
}
SecondView
import SwiftUI
struct SecondView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
Spacer(minLength: 50.0)
Button(action: {self.viewRouter.currentPage = "view1"}) {
BackButtonContent()
}
}
}
}
#if DEBUG
struct SecondView_Previews : PreviewProvider {
static var previews: some View {
SecondView().environmentObject(ViewRouter())
}
}
#endif
struct BackButtonContent : View {
var body: some View {
return Text("Back")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.blue)
.cornerRadius(15)
.padding(.top, 50)
}
}
Hope this helps!
Simplest and most effective solution is :
NavigationLink(destination:ScoresTableView()) {
Text("Scores")
}.navigationBarHidden(true)
.frame(width: 90, height: 45, alignment: .center)
.foregroundColor(.white)
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(10)
.contentShape(Rectangle())
.padding(EdgeInsets(top: 16, leading: UIScreen.main.bounds.size.width - 110 , bottom: 16, trailing: 20))
ScoresTableView is the destination view.
In my opinion a cleaner way for iOS 16+ is using a state bool to present the view.
struct ButtonNavigationView: View {
#State private var isShowingSecondView : Bool = false
var body: some View {
NavigationStack {
VStack{
Button(action:{isShowingSecondView = true} ){
Text("Show second view")
}
}.navigationDestination(isPresented: $isShowingSecondView) {
Text("SecondView")
}
}
}
}
I think above answers are nice, but simpler way should be:
NavigationLink {
TargetView()
} label: {
Text("Click to go")
}
Need help with this please.
I have a view with 2 date variables and I want to show a modal which have the datepicker and let user pick different dates for these variables.
Currently I have two buttons that show the same sheet but pass different variable to the modal.
The problem the variable don’t update after dismissing the modal.
import SwiftUI
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
#State private var secOneDate = Date()
#State private var secTwoDate = Date()
#State private var isDatepickerPresented = false
var body: some View {
VStack {
HStack{
Button{
isDatepickerPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.indigo)
}
.sheet(isPresented: $isDatepickerPresented){
DatePickView(selectDate: $secOneDate)
}
Text("SecOneDate: \(secOneDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
HStack{
Button{
isDatepickerPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.mint)
}
.sheet(isPresented: $isDatepickerPresented)
{
DatePickView(selectDate: $secTwoDate)
}
Text("SecTwoDate: \(secTwoDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
}
}
}
import SwiftUI
struct DatePickView: View {
#Environment(\.dismiss) private var dismiss
#Binding var selectDate: Date
var body: some View {
VStack(alignment: .center, spacing: 20) {
HStack {
Text("\(selectDate)")
.padding()
Spacer()
Button {
dismiss()
} label: {
Image(systemName: "delete.backward.fill")
.foregroundColor(.indigo)
}
}.padding()
DatePicker("", selection: $selectDate)
.datePickerStyle(.graphical)
}
}
}
First of all, thank you for your minimal, reproducible example: it is clear and can be immediately used for debugging. Answering to your question:
The problem with your code is that you have only one variable that opens the sheet for both dates. Even though you are correctly passing the two different #Bindings, when you toggle isDatepickerPresented you are asking SwiftUI to show both sheets, but this will never happen. Without knowing, you are always triggering the first of the sheet presentations - the one that binds secOneDate. The sheet that binds secTwoDate is never shown because you can't have two sheets simultaneously.
With that understanding, the solution is simple: use two different trigger variables. Here's the code corrected (DatePickView doesn't change):
struct Example: View {
#State private var secOneDate = Date()
#State private var secTwoDate = Date()
#State private var isDatepickerOnePresented = false
#State private var isDatepickerTwoPresented = false
var body: some View {
VStack {
HStack{
Button{
isDatepickerOnePresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.indigo)
}
.sheet(isPresented: $isDatepickerOnePresented){
DatePickView(selectDate: $secOneDate)
}
Text("SecOneDate: \(secOneDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
HStack{
Button{
isDatepickerTwoPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.mint)
}
.sheet(isPresented: $isDatepickerTwoPresented) {
DatePickView(selectDate: $secTwoDate)
}
Text("SecTwoDate: \(secTwoDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
}
}
}
I am trying to implement a simple "tap to toggle the visibility of the UI" in SwiftUI with fade in/out animation. The following code animates the fade-in effect of the Text element as I expected, but it immediately hides the Text element when isVisible become false.
I'd like to understand why this code does not work, and how to fix it in the most natural way.
import SwiftUI
struct ContentView: View {
#State var isVisible = true
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.blue)
.gesture(TapGesture(count: 1).onEnded {
withAnimation(.easeInOut(duration: 1.0)) {
isVisible.toggle()
}
})
if isVisible {
Text("Tap me!")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm using Xcode 12.5 on Big Sur, and my iPhone is running iOS 14.5.1.
Thanks to Erik Philips, here is the answer.
import SwiftUI
struct ContentView: View {
#State var isVisible = true
var body: some View {
ZStack {
Rectangle()
.zIndex(1)
.foregroundColor(.blue)
.gesture(TapGesture(count: 1).onEnded {
withAnimation(.easeInOut(duration: 1.0)) {
isVisible.toggle()
}
})
if isVisible {
Text("Tap me!")
.zIndex(2)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I would like to open new View after clicking on a image button in SwiftUI, any idea?
Button(action: {}) {
Image("gift")
Text("Send")
.padding(.horizontal)
}
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
If you want it in a NavigationView, you can use a NavigationLink instead of a Button:
struct ContentView : View {
var body: some View {
NavigationView {
NavigationLink(destination: DetailView()) {
Image("gift")
Text("Send")
.padding(.horizontal)
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
}
}
}
}
struct DetailView : View {
var body: some View {
Text("Detail")
}
}
If you want a sheet presented, you can use the sheet modifier and a #State variable:
struct ContentView : View {
#State private var sheetPresented = false
var body: some View {
Button(action: {
sheetPresented = true
}) {
Image("gift")
Text("Send")
.padding(.horizontal)
}
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
.sheet(isPresented: $sheetPresented) {
DetailView()
}
}
}
struct DetailView : View {
var body: some View {
Text("Detail")
}
}
I want to work with an image that you take with camera, explicitly, I want work with matrix image, change pixel...
That's my code which allow you to take a picture.
import SwiftUI
struct ContentView: View {
#State private var showImagePicker: Bool = false
#State private var image: Image? = nil
var body: some View {
VStack {
image?.resizable()
.scaledToFit()
.padding(.top,100)
.frame(width: 300.0, height: 300.0, alignment: .top)
Button(action: {self.showImagePicker = true
}) {HStack {
Image(systemName: "camera.on.rectangle.fill")
.font(.title)
Text("Take picture")
.fontWeight(.semibold)
.font(.title)
}
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(40)}
}
.padding(.bottom,400)
.sheet(isPresented: self.$showImagePicker) {
PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is PhotoCaptureView
import Foundation
import SwiftUI
struct PhotoCaptureView: View {
#Binding var showImagePicker: Bool
#Binding var image: Image?
var body: some View {
ImagePicker(isShown: $showImagePicker, image: $image)
}
}
#if DEBUG
struct PhotoCaptureView_Previews: PreviewProvider {
static var previews: some View {
PhotoCaptureView(showImagePicker: .constant(false), image: .constant(Image("")))
}
}
#endif
I have to return the image, I guess that it is in struct PhotoCaptureView.
How to retrieve this image and work with ?
Thank you.
You can use onDismiss completionHandler of the sheet to work on image after it is selected.
.sheet(isPresented: Binding<Bool>.constant(true), onDismiss: {
if let image = self.image {
processor.process(image)
}
}, content: { () -> View in
PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image)
})