On tap gesture on an image SwiftUI - swiftui

So I am trying to change between views when someone clicks on the image, but the action after the ontapgesture is always "expression is unused". I tried changing it to a navigation view as well as other things but I feel like nothing seems to be working.
Code below:
struct MenuView2: View {
#State private var menuu2 = menu2()
var body: some View {
ScrollView(){
VStack {
ZStack {
Rectangle().frame(height:40).opacity(0.25).blur(radius: 10).onTapGesture {
print("breakfast tapped ")
}
HStack {
VStack(alignment: .leading, spacing: 8, content: {
Text("Breakfast").font(.largeTitle)
})
}
}
Image("breakfast").resizable().scaledToFill().onTapGesture {
menuu2
}
}
}
}
}
Thank you.

The error you get is "correct" in that menuu2 does not do anything, it is just there.
There are a number of ways to change view on tap, this is just one way:
struct MenuView2: View {
#State private var menuu2 = menuu2()
#State private var changeView = false
var body: some View {
changeView ? AnyView(theOtherView) : AnyView(theScrollView)
}
var theOtherView: some View {
// menuu2 presumably
// just for testing
Text("theOtherView").onTapGesture {
self.changeView = false
}
}
var theScrollView: some View {
ScrollView() {
VStack {
ZStack {
Rectangle().frame(height:40).opacity(0.25).blur(radius: 10).onTapGesture {
print("breakfast tapped ")
}
HStack {
VStack(alignment: .leading, spacing: 8, content: {
Text("Breakfast").font(.largeTitle)
})
}
}
Image("breakfast").resizable().scaledToFill().onTapGesture {
self.changeView = true
}
}
}
}
}

Related

SwiftUI Observed Object not updating

Really just starting out with SwiftUI and trying to get my head around MVVM.
The code below displays a height and 4 toggle buttons, thus far I have only connected 2.
Major up and Major Down.
When clicked I see in the console that the value is altered as expected.
What I don't see is the the main display updating to reflect the change.
I have tried refactoring my code to include the view model into each Struct but still not seeing the change.
I think I have covered the basics but am stumped, I'm using a single file for now but plan to move the Model and ViewModel into separate files when I have a working mockup.
Thanks for looking.
import SwiftUI
/// This is our "ViewModel"
class setHeightViewModel: ObservableObject {
struct ImperialAndMetric {
var feet = 16
var inches = 3
var meters = 4
var CM = 95
var isMetric = true
}
// The model should be Private?
// ToDo: Fix the private issue.
#Published var model = ImperialAndMetric()
// Our getters for the model
var feet: Int { return model.feet }
var inches: Int { return model.inches }
var meters: Int { return model.meters }
var cm: Int { return model.CM }
var isMetric: Bool { return model.isMetric }
/// Depending upon the selected mode, move the major unit up by one.
func majorUp() {
if isMetric == true {
model.meters += 1
print("Meters is now: \(meters)")
} else {
model.feet += 1
print("Feet is now: \(feet)")
}
}
/// Depending upon the selected mode, move the major unit down by one.
func majorDown() {
if isMetric == true {
model.meters -= 1
print("Meters is now: \(meters)")
} else {
model.feet -= 1
print("Feet is now: \(feet)")
}
}
/// Toggle the state of the display mode.
func toggleMode() {
model.isMetric = !isMetric
}
}
// This is our View
struct ViewSetHeight: View {
// UI will watch for changes for setHeihtVM now.
#ObservedObject var setHeightVM = setHeightViewModel()
var body: some View {
NavigationView {
Form {
ModeArea(viewModel: setHeightVM)
SelectionUp(viewModel: setHeightVM)
// Show the correct height format
if self.setHeightVM.isMetric == true {
ValueRowMetric(viewModel: self.setHeightVM)
} else {
ValueRowImperial(viewModel: self.setHeightVM)
}
SelectionDown(viewModel: setHeightVM)
}.navigationTitle("Set the height")
}
}
}
struct ModeArea: View {
var viewModel: setHeightViewModel
var body: some View {
Section {
if viewModel.isMetric == true {
SwitchImperial(viewModel: viewModel)
} else {
SwitchMetric(viewModel: viewModel)
}
}
}
}
struct SwitchImperial: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Button(action: {
print("Imperial Tapped")
}, label: {
Text("Imperial").onTapGesture {
viewModel.toggleMode()
}
})
Spacer()
Text("\(viewModel.feet)\'-\(viewModel.inches)\"").foregroundColor(.gray)
}
}
}
struct SwitchMetric: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Button(action: {
print("Metric Tapped")
}, label: {
Text("Metric").onTapGesture {
viewModel.toggleMode()
}
})
Spacer()
Text("\(viewModel.meters).\(viewModel.cm) m").foregroundColor(.gray)
}
}
}
struct SelectionUp: View {
var viewModel: setHeightViewModel
var body: some View {
Section {
HStack {
Button(action: {
print("Major Up Tapped")
viewModel.majorUp()
}, label: {
Image(systemName: "chevron.up").padding()
})
Spacer()
Button(action: {
print("Minor Up Tapped")
}, label: {
Image(systemName: "chevron.up").padding()
})
}
}
}
}
struct ValueRowImperial: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Spacer()
Text(String(viewModel.feet)).accessibility(label: Text("Feet"))
Text("\'").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
Text("-").foregroundColor(Color.gray).padding(.horizontal, -10.0)
Text(String(viewModel.inches)).accessibility(label: Text("Inches"))
Text("\"").foregroundColor(Color.gray).padding(.horizontal, -10.0).padding(.top, -15.0)
Spacer()
}.font(.largeTitle).padding(.zero)
}
}
struct ValueRowMetric: View {
var viewModel: setHeightViewModel
var body: some View {
HStack {
Spacer()
Text(String(viewModel.meters)).accessibility(label: Text("Meter"))
Text(".").padding(.horizontal, -5.0).padding(.top, -15.0)
Text(String(viewModel.cm)).accessibility(label: Text("CM"))
Text("m").padding(.horizontal, -5.0).padding(.top, -15.0).font(.body)
Spacer()
}.font(.largeTitle)
}
}
struct SelectionDown: View {
var viewModel: setHeightViewModel
var body: some View {
Section {
HStack {
Button(action: {
print("Major Down Tapped")
viewModel.majorDown()
}, label: {
Image(systemName: "chevron.down").padding()
})
Spacer()
Button(action: {
print("Minor Down Tapped")
}, label: {
Image(systemName: "chevron.down").padding()
})
}
}
}
}
At the moment you receive the setHeightViewModel in various views as a var,
you should receive it as an ObservedObject.
I suggest you try this, in ViewSetHeight
#StateObject var setHeightVM = setHeightViewModel()
and in all your other views where you pass this model, use:
#ObservedObject var viewModel: setHeightViewModel
such as in ModeArea, SwitchImperial, SwitchMetric, SelectionUp, etc...
Alternatively you could use #EnvironmentObject ... to pass the setHeightViewModel
to all views that needs it.

Popover displaying inaccurate information inside ForEach

I'm having a problem where I have a ForEach loop inside a NavigationView. When I click the Edit button, and then click the pencil image at the right hand side on each row, I want it to display the text variable we are using from the ForEach loop. But when I click the pencil image for the text other than test123, it still displays the text test123 and I have absolutely no idea why.
Here's a video. Why is this happening?
import SwiftUI
struct TestPopOver: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var showThemeEditor = false
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.showThemeEditor = true
}
}
}
}
}
.popover(isPresented: $showThemeEditor) {
CustomPopOver(isShowing: $showThemeEditor, text: text)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}
}
}
struct CustomPopOver: View {
#Binding var isShowing: Bool
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.isShowing = false
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
This is a very common issue (especially since iOS 14) that gets run into a lot with sheet but affects popover as well.
You can avoid it by using popover(item:) rather than isPresented. In this scenario, it'll actually use the latest values, not just the one that was present when then view first renders or when it is first set.
struct EditItem : Identifiable { //this will tell it what sheet to present
var id = UUID()
var str : String
}
struct ContentView: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var editItem : EditItem? //the currently presented sheet -- nil if no sheet is presented
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.editItem = EditItem(str: text) //set the current item
}
}
}
}
}
.popover(item: $editItem) { item in //item is now a reference to the current item being presented
CustomPopOver(text: item.str)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct CustomPopOver: View {
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.presentationMode.wrappedValue.dismiss()
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
I also opted to use the presentationMode environment property to dismiss the popover, but you could pass the editItem binding and set it to nil as well (#Binding var editItem : EditItem? and editItem = nil). The former is just a little more idiomatic.

SwiftUI no hide animation

I have noticed that when I'm coloring the background I will not get animations when removing Views.
If I remove Color(.orange).edgesIgnoringSafeArea(.all) then hide animation will work, otherwise Modal will disappear abruptly. Any solutions?
struct ContentView: View {
#State var show = false
func toggle() {
withAnimation {
show = true
}
}
var body: some View {
ZStack {
Color(.orange).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Modal")
}
if show {
Modal(show: $show)
}
}
}
}
struct Modal: View {
#Binding var show: Bool
func toggle() {
withAnimation {
show = false
}
}
var body: some View {
ZStack {
Color(.systemGray4).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Close")
}
}
}
}
You need to make animatable container holding removed view (and this makes possible to keep animation in one place). Here is possible solution.
Tested with Xcode 12 / iOS 14
struct ContentView: View {
#State var show = false
func toggle() {
show = true // animation not requried
}
var body: some View {
ZStack {
Color(.orange).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Modal")
}
VStack { // << major changes
if show {
Modal(show: $show)
}
}.animation(.default) // << !!
}
}
}
struct Modal: View {
#Binding var show: Bool
func toggle() {
show = false // animation not requried
}
var body: some View {
ZStack {
Color(.systemGray4).edgesIgnoringSafeArea(.all)
Button(action: toggle) {
Text("Close")
}
}
}
}

How to go to another view with button click

I have a button in my code and I have a file called LogindView.swift
I cannot get the code to open another view file when clicking on the button.
Can anybody give me an example on how to do it.
In my button action I have tried to write LogindView() but i just gives me a warning.
"Result of 'LogindView' initializer is unused"
Button(action: {
// Do action
LogindView()
}, label: {
//** Label text
Text("Logind")
.font(.headline)
.padding(.all)
.foregroundColor(Color.white)
})
.background(Color.blue)
You essentially have 3 options to transition between views depending on your needs.
First, you can use a NavigationView. This will provide a back button and will allow the user to go back. Note that there are some bugs currently when you don't put the NavigationLink inside of a List as per https://stackoverflow.com/a/57122621/3179416
import SwiftUI
struct MasterView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: LoginView()) {
Text("Login")
}
}
.navigationBarTitle(Text("Master"))
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
Second, you can present a modal using .sheet. This will present a modal that appears on top of the current view but it can be dismissed by the user by dragging it down.
import SwiftUI
struct MasterView: View {
#State var isModal: Bool = false
var body: some View {
Button("Login") {
self.isModal = true
}.sheet(isPresented: $isModal, content: {
LoginView()
})
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
Third, you can just use an if statement to change the current view to your Login View like so
import SwiftUI
struct MasterView: View {
#State var showLoginView: Bool = false
var body: some View {
VStack {
if showLoginView {
LoginView()
} else {
Button("Login") {
self.showLoginView = true
}
}
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
If you would like to animate this, so that the transition doesn't appear so abruptly, you can also do this:
import SwiftUI
struct MasterView: View {
#State var showLoginView: Bool = false
var body: some View {
VStack {
if showLoginView {
LoginView()
.animation(.spring())
.transition(.slide)
} else {
Button("Login") {
withAnimation {
self.showLoginView = true
}
}.animation(.none)
}
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
You can use navigation link instead button
var body: some View {
VStack {
Text("Title")
.font(.headline)
Image("myimage").clipShape(Circle())
Text("mytext").font(.title)
NavigationLink(destination: AnotherView()) {
Image(systemName: "person.circle").imageScale(.large)
}
}
}

Show a new View from Button press Swift UI

I would like to be able to show a new view when a button is pressed on one of my views.
From the tutorials I have looked at and other answered questions here it seems like everyone is using navigation button within a navigation view, unless im mistaken navigation view is the one that gives me a menu bar right arrows the top of my app so I don't want that. when I put the navigation button in my view that wasn't a child of NavigationView it was just disabled on the UI and I couldn't click it, so I guess I cant use that.
The other examples I have seen seem to use presentation links / buttons which seem to show a sort of pop over view.
Im just looking for how to click a regular button and show another a view full screen just like performing a segue used to in the old way of doing things.
Possible solutions
1.if you want to present on top of current view(ex: presentation style in UIKit)
struct ContentView: View {
#State var showingDetail = false
var body: some View {
Button(action: {
self.showingDetail.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $showingDetail) {
DetailView()
}
}
}
2.if you want to reset current window scene stack(ex:after login show home screen)
Button(action: goHome) {
HStack(alignment: .center) {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
func goHome() {
if let window = UIApplication.shared.windows.first {
window.rootViewController = UIHostingController(rootView: HomeScreen())
window.makeKeyAndVisible()
}
}
3.push new view (ex: list->detail, navigation controller of UIKit)
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Show Detail View")
}.navigationBarTitle("Navigation")
}
}
}
}
4.update the current view based on #state property, (ex:show error message on login failure)
struct ContentView: View {
#State var error = true
var body: some View {
...
... //login email
.. //login password
if error {
Text("Failed to login")
}
}
}
For simple example you can use something like below
import SwiftUI
struct ExampleFlag : View {
#State var flag = true
var body: some View {
ZStack {
if flag {
ExampleView().tapAction {
self.flag.toggle()
}
} else {
OtherExampleView().tapAction {
self.flag.toggle()
}
}
}
}
}
struct ExampleView: View {
var body: some View {
Text("some text")
}
}
struct OtherExampleView: View {
var body: some View {
Text("other text")
}
}
but if you want to present more view this way looks nasty
You can use stack to control view state without NavigationView
For Example:
class NavigationStack: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
var list: [AuthState] = []
public func push(state: AuthState) {
list.append(state)
didChange.send()
}
public func pop() {
list.removeLast()
didChange.send()
}
}
enum AuthState {
case mainScreenState
case userNameScreen
case logginScreen
case emailScreen
case passwordScreen
}
struct NavigationRoot : View {
#EnvironmentObject var state: NavigationStack
#State private var aligment = Alignment.leading
fileprivate func CurrentView() -> some View {
switch state.list.last {
case .mainScreenState:
return AnyView(GalleryState())
case .none:
return AnyView(LoginScreen().environmentObject(state))
default:
return AnyView(AuthenticationView().environmentObject(state))
}
}
var body: some View {
GeometryReader { geometry in
self.CurrentView()
.background(Image("background")
.animation(.fluidSpring())
.edgesIgnoringSafeArea(.all)
.frame(width: geometry.size.width, height: geometry.size.height,
alignment: self.aligment))
.edgesIgnoringSafeArea(.all)
.onAppear {
withAnimation() {
switch self.state.list.last {
case .none:
self.aligment = Alignment.leading
case .passwordScreen:
self.aligment = Alignment.trailing
default:
self.aligment = Alignment.center
}
}
}
}
.background(Color.black)
}
}
struct ExampleOfAddingNewView: View {
#EnvironmentObject var state: NavigationStack
var body: some View {
VStack {
Button(action:{ self.state.push(state: .emailScreen) }){
Text("Tap me")
}
}
}
}
struct ExampleOfRemovingView: View {
#EnvironmentObject var state: NavigationStack
var body: some View {
VStack {
Button(action:{ self.state.pop() }){
Text("Tap me")
}
}
}
}
In my opinion this bad way, but navigation in SwiftUI much worse