Why Property Observer does not work for Picker in SwiftUI - ios

when I change the selecteddrive by using a button(ontapGesture),it prints "value changed",but when I use the Picker to change the value, the didset function did not work.
enum WheelDrive: String, CaseIterable, Identifiable {
case frontdrive
case reardrive
case Fourwheeldrive
case Neutral
var id: String { self.rawValue }
}
#State var selecteddrive = WheelDrive.Fourwheeldrive {
willSet(newValue){ print("value changed")}
}
Picker(selection: $selecteddrive, label: Text("WheelDrive")) {
Text("前驱").tag(WheelDrive.frontdrive)
.foregroundColor(.white)
Text("后驱").tag(WheelDrive.reardrive)
.foregroundColor(.white)
Text("四驱").tag(WheelDrive.Fourwheeldrive)
.foregroundColor(.white)
Text("滑行").tag(WheelDrive.Neutral)
.foregroundColor(.white)
}

Use custom binding
Picker(selection: Binding(get: {selecteddrive}, set: {selecteddrive = $0}), label: Text("WheelDrive")) { //<-- Here
Text("前驱").tag(WheelDrive.frontdrive)
.foregroundColor(.white)
Text("后驱").tag(WheelDrive.reardrive)
.foregroundColor(.white)
Text("四驱").tag(WheelDrive.Fourwheeldrive)
.foregroundColor(.white)
Text("滑行").tag(WheelDrive.Neutral)
.foregroundColor(.white)
}
Or you can use .onChange for capturing value.
Picker(selection: $selecteddrive, label: Text("WheelDrive")) {
Text("前驱").tag(WheelDrive.frontdrive)
.foregroundColor(.white)
Text("后驱").tag(WheelDrive.reardrive)
.foregroundColor(.white)
Text("四驱").tag(WheelDrive.Fourwheeldrive)
.foregroundColor(.white)
Text("滑行").tag(WheelDrive.Neutral)
.foregroundColor(.white)
}
.onChange(of: selecteddrive, perform: { (selecteddrive) in //<- Here
print(selecteddrive)
})

Related

SwiftUI: Slider in List/ForEach behaves strangely

It's very hard to explain without a recording from a second device that I don't have, but when I try to slide my slider, it will stop when my finger is definitely still moving.
I have my code posted below. I'd be happy to answer any questions and explain whatever. I'm sure it's something really simple that I should know. Any help would be very much appreciated, thanks!
import SwiftUI
class SettingsViewModel: ObservableObject {
#Published var selectedTips = [
10.0,
15.0,
18.0,
20.0,
25.0
]
func addTip() {
selectedTips.append(0.0)
selectedTips.sort()
}
func removeTip(index: Int) {
selectedTips.remove(at: index)
selectedTips = selectedTips.compactMap{ $0 }
}
}
struct SettingsTipsView: View {
#StateObject var model = SettingsViewModel()
var body: some View {
List {
HStack {
Text("Edit Suggested Tips")
.font(.title2)
.fontWeight(.semibold)
Spacer()
if(model.selectedTips.count < 5) {
Button(action: { model.addTip() }, label: {
Image(systemName: "plus.circle.fill")
.renderingMode(.original)
.font(.title3)
.padding(.horizontal, 10)
})
.buttonStyle(BorderlessButtonStyle())
}
}
ForEach(model.selectedTips, id: \.self) { tip in
let i = model.selectedTips.firstIndex(of: tip)!
//If I don't have this debug line here then the LAST slider in the list tries to force the value to 1 constantly, even if I remove the last one, the new last slider does the same. It's from a separate file but it's pretty much the same as the array above. An explanation would be great.
Text("\(CalculatorViewModel.suggestedTips[i])")
HStack {
Text("\(tip, specifier: "%.0f")%")
Slider(value: $model.selectedTips[i], in: 1...99, label: { Text("Label") })
if(model.selectedTips.count > 1) {
Button(action: { model.removeTip(index: i) }, label: {
Image(systemName: "minus.circle.fill")
.renderingMode(.original)
.font(.title3)
.padding(.horizontal, 10)
})
.buttonStyle(BorderlessButtonStyle())
}
}
}
}
}
}
Using id: \.self within a List or ForEach is a dangerous idea in SwiftUI. The system uses it to identify what it expects to be unique elements. But, as soon as you move the slider, you have a change of ending up with a tip value that is equal to another value in the list. Then, SwiftUI gets confused about which element is which.
To fix this, you can use items with truly unique IDs. You should also try to avoid using indexes to refer to certain items in the list. I've used list bindings to avoid that issue.
struct Tip : Identifiable {
var id = UUID()
var tip : Double
}
class SettingsViewModel: ObservableObject {
#Published var selectedTips : [Tip] = [
.init(tip:10.0),
.init(tip:15.0),
.init(tip:18.0),
.init(tip:20.0),
.init(tip:25.0)
]
func addTip() {
selectedTips.append(.init(tip:0.0))
selectedTips = selectedTips.sorted(by: { a, b in
a.tip < b.tip
})
}
func removeTip(id: UUID) {
selectedTips = selectedTips.filter { $0.id != id }
}
}
struct SettingsTipsView: View {
#StateObject var model = SettingsViewModel()
var body: some View {
List {
HStack {
Text("Edit Suggested Tips")
.font(.title2)
.fontWeight(.semibold)
Spacer()
if(model.selectedTips.count < 5) {
Button(action: { model.addTip() }, label: {
Image(systemName: "plus.circle.fill")
.renderingMode(.original)
.font(.title3)
.padding(.horizontal, 10)
})
.buttonStyle(BorderlessButtonStyle())
}
}
ForEach($model.selectedTips, id: \.id) { $tip in
HStack {
Text("\(tip.tip, specifier: "%.0f")%")
.frame(width: 50) //Otherwise, the width changes while moving the slider. You could get fancier and try to use alignment guides for a more robust solution
Slider(value: $tip.tip, in: 1...99, label: { Text("Label") })
if(model.selectedTips.count > 1) {
Button(action: { model.removeTip(id: tip.id) }, label: {
Image(systemName: "minus.circle.fill")
.renderingMode(.original)
.font(.title3)
.padding(.horizontal, 10)
})
.buttonStyle(BorderlessButtonStyle())
}
}
}
}
}
}

SwiftUI having trouble switching #State and #Binding variables based of if statement

I'm trying to allow the FileManager to check if the matching Image Selected is saved. When saved, it needs to update the views on both the MainScreenView and BadgeScreenView. I am getting a error that "Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update." and "Result of 'BadgeScreenView' initializer is unused" after the image is selected and checked to see if it is saved at the right locations.
var ContentViewBadge = UIImage(systemName: "questionmark")!
var fileURL: URL?
func saveImage() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Compliance")
.appendingPathExtension("png")
fileURL = furl
try ContentViewBadge.pngData()?.write(to: furl)
print("Image \(ContentViewBadge) is saved to \(furl)")
} catch {
print("could not create imageFile")
}
let finding = fileURL
let fileExists = FileManager().fileExists(atPath: finding!.path)
if fileExists {
#State var IsTrue: Bool = true
BadgeScreenView(TrueBadge: $IsTrue)
//Change State variable "TrueBadge" here
print("Found something!")
}
}
import SwiftUI
import Foundation
var IsDone = false
struct BadgeScreenView: View {
#Binding var TrueBadge: Bool //Need help switching this Binding to true
#State private var ComplianceBadgeIsPicking = UIImage(named: "BlankComplianceBadge")!
#State private var isShwoingPhotoPicker = false
#State private var ShowInstruction = false
#State private var AlertToReplaceBade = false
var body: some View {
//The beginning
if TrueBadge {
Color("MainBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Text("Clearance Status")
.font(.title)
.fontWeight(.semibold)
.offset(y: -15)
.foregroundColor(.white)
Text("Vaccine Compliant")
.foregroundColor(.white)
.bold()
.font(.system(size: 30))
Image(uiImage: ContentViewBadge)
.resizable()
.aspectRatio(contentMode: .fit)
.scaledToFit()
Button(action: {
AlertToReplaceBade.toggle()
}) {
Image(systemName: "trash" )
Text("Remove")
}
.foregroundColor(.white)
.padding()
.background(Color.red)
.cornerRadius(15)
.offset(y: 13)
}.alert(isPresented: $AlertToReplaceBade, content: {
Alert(title: Text("Are you sure you would like to remove your current badge?"),
message: Text("Remeber that this badge is and will be permanently removed"),
primaryButton: .default(Text("Yes"), action: {
// Somehow need to remove the image and activate the UIImagePickerController
isShwoingPhotoPicker.toggle()
}), secondaryButton: .cancel(Text("No, I do not")))
}).sheet(isPresented: $isShwoingPhotoPicker, content: {
PhotoPicker(Badge: $ComplianceBadgeIsPicking)
})
)}
else {
Color("ExpiredBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Image(systemName: "person.crop.circle.badge.questionmark.fill")
.font(.system(size:150))
.offset(y: -10)
.foregroundColor(.black)
Text("Compliance Badge")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.black)
.offset(y: -2)
Text("You do not have a current vaccine compliant badge. Please upload one that shows you are vaccine compliant or within 'green' status")
.font(.system(size: 15))
.foregroundColor(.black)
.fontWeight(.bold)
.multilineTextAlignment(.center)
.frame(width: 270, height: 140, alignment: .center)
.offset(y: -26)
Button(action: {
ShowInstruction.toggle()
}) {
Image(systemName: "questionmark.circle")
Text("How to upload")
.bold()
.font(.system(size:20))
}
.offset(y: -40)
Button(action: {
isShwoingPhotoPicker.toggle()
}) {
Image(systemName: "square.and.arrow.up")
Text("Upload Badge")
.bold()
.font(.system(size:20))
}
.offset(y: -10)
}.sheet(isPresented: $ShowInstruction, content: {
Instruction()
})
.sheet(isPresented: $isShwoingPhotoPicker, content: {
PhotoPicker(Badge: $ComplianceBadgeIsPicking)
})
.accentColor(.black)
)
}
//The End
}
}
There's not enough for a minimal reproducible example in your code, so I had to make some guesses here, but this is the gist of what I'd expect things to look like. Note that saveImage is inside the View and thus has access to change the state.
It's not clear to me, though, where you call saveImage (you don't do it anywhere in your included code), which could effect things further.
let contentViewBadge = UIImage(systemName: "questionmark")!
struct BadgeScreenView: View {
#Binding var trueBadge: Bool
#State private var complianceBadgeIsPicking = UIImage(named: "BlankComplianceBadge")!
#State private var isShowingPhotoPicker = false
#State private var showInstruction = false
#State private var alertToReplaceBade = false
func saveImage() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Compliance")
.appendingPathExtension("png")
try contentViewBadge.pngData()?.write(to: furl)
print("Image \(contentViewBadge) is saved to \(furl)")
let fileExists = FileManager().fileExists(atPath: furl.path)
if fileExists {
trueBadge = true
print("Found something!")
}
} catch {
print("could not create imageFile")
}
}
var body: some View {
//The beginning
if trueBadge {
Color("MainBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Text("Clearance Status")
.font(.title)
.fontWeight(.semibold)
.offset(y: -15)
.foregroundColor(.white)
Text("Vaccine Compliant")
.foregroundColor(.white)
.bold()
.font(.system(size: 30))
Image(uiImage: contentViewBadge)
.resizable()
.aspectRatio(contentMode: .fit)
.scaledToFit()
Button(action: {
alertToReplaceBade.toggle()
}) {
Image(systemName: "trash" )
Text("Remove")
}
.foregroundColor(.white)
.padding()
.background(Color.red)
.cornerRadius(15)
.offset(y: 13)
}.alert(isPresented: $alertToReplaceBade, content: {
Alert(title: Text("Are you sure you would like to remove your current badge?"),
message: Text("Remeber that this badge is and will be permanently removed"),
primaryButton: .default(Text("Yes"), action: {
// Somehow need to remove the image and activate the UIImagePickerController
isShowingPhotoPicker.toggle()
}), secondaryButton: .cancel(Text("No, I do not")))
}).sheet(isPresented: $isShowingPhotoPicker, content: {
PhotoPicker(Badge: $complianceBadgeIsPicking)
})
)}
else {
Color("ExpiredBadgeScreen")
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
Image(systemName: "person.crop.circle.badge.questionmark.fill")
.font(.system(size:150))
.offset(y: -10)
.foregroundColor(.black)
Text("Compliance Badge")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.black)
.offset(y: -2)
Text("You do not have a current vaccine compliant badge. Please upload one that shows you are vaccine compliant or within 'green' status")
.font(.system(size: 15))
.foregroundColor(.black)
.fontWeight(.bold)
.multilineTextAlignment(.center)
.frame(width: 270, height: 140, alignment: .center)
.offset(y: -26)
Button(action: {
showInstruction.toggle()
}) {
Image(systemName: "questionmark.circle")
Text("How to upload")
.bold()
.font(.system(size:20))
}
.offset(y: -40)
Button(action: {
isShwoingPhotoPicker.toggle()
}) {
Image(systemName: "square.and.arrow.up")
Text("Upload Badge")
.bold()
.font(.system(size:20))
}
.offset(y: -10)
}.sheet(isPresented: $showInstruction, content: {
Instruction()
})
.sheet(isPresented: $isShowingPhotoPicker, content: {
PhotoPicker(Badge: $complianceBadgeIsPicking)
})
.accentColor(.black)
)
}
//The End
}
}
An alternative (or addition) to putting savingImage inside the view would be to explicitly pass a binding to it. The function would look like this:
func saveImage(binding: Binding<Bool>) {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Compliance")
.appendingPathExtension("png")
try contentViewBadge.pngData()?.write(to: furl)
print("Image \(contentViewBadge) is saved to \(furl)")
let fileExists = FileManager().fileExists(atPath: furl.path)
if fileExists {
binding.wrappedValue = true
print("Found something!")
}
} catch {
print("could not create imageFile")
}
}
And you would call it like this:
saveImage(binding: $trueBadge)
Note: I changed your variable names to fit the Swift conventions of using lowercase letters to start variable/property names

how toshow alert in SwiftUI that Email and Password are incorrect?

Good Evening,
I have a question, I need to show an error when a user enters a wrong email or enters a wrong password. What do I need to add in my code?
I am new to software development and Sam not so familiar with swift ui could someone help me?
#State var email = ""
#State var password = ""
var body: some View {
Image("ISD-Logo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 185, height: 140)
.clipped()
.padding(.bottom, 75)
VStack {
TextField("Email", text: $email)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("password", text: $password)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: { login() }) {
Text("Sign in")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.black)
.cornerRadius(35.0)
}
}
.padding()
}
// Login and error message
func login() {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
print(error?.localizedDescription ?? "")
} else {
print("success")
}
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
What kind of alert do you want?
You can do that:
struct ContentView: View {
#State private var showingAlert = false
var body: some View {
Button("Show Alert") {
showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
}
}
You can try like this. I would suggest you go to SWIFT UI basics first
#State var email = ""
#State var password = ""
var body: some View {
Image("ISD-Logo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 185, height: 140)
.clipped()
.padding(.bottom, 75)
VStack {
TextField("Email", text: $email)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("password", text: $password)
.padding()
.background(Color(UIColor.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: { login() }) {
Text("Sign in")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.black)
.cornerRadius(35.0)
}
}
.padding()
}
// Login and error message
func login() {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
self.isAlert = true
print(error?.localizedDescription ?? "")
} else {
print("success")
}
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}

SwiftUI automatically go to next view on success

I have a login view that does an http request . Once we get the http response we know whether the user can go to the next view . I am wondering how can I trigger the next view without click ? I am not looking to do a sheet since I want to get the full screen mode . I have been looking at this Go to a new view using SwiftUI but no luck . From my code below when I click on Press on me navigationLink I can go to the correct view, however I need that same functionality to work without clicking in the http response below where it says decode.status == 1 because the user has authenticated successfully .
struct ContentView: View {
#State var email: String = ""
#State var password: String = ""
#State var message: String = ""
#State var status: Int = -10
#State var showActive = true
var body: some View {
NavigationView {
ZStack {
Color(UIColor.systemGroupedBackground)
.edgesIgnoringSafeArea(.all)
VStack(alignment: .center) {
Spacer()
NavigationLink(destination: MainView()) {
Text("Press on me")
}.buttonStyle(PlainButtonStyle()) // This works when clicked
TextField("Email", text: $email)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
SecureField("Password", text: $password)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
Button(action: {
guard let url = URL(string:ConnectionString+"profile/login") else { return }
let parameter = "email=\(self.email)&password=\(self.password)"
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
URLSession.shared.dataTask(with:request, completionHandler: {(data, response, error) in
if let decode = try? JSONDecoder().decode(ProfileCodable.self, from: data!)
{
self.status = decode.status ?? -10
self.message = decode.message ?? ""
if decode.status == 0 {
print("Invalid Credentials")
} else if decode.status == 1 {
// ** Go to next View here **
} else if decode.status == -1 {
print("Error")
}
} else {
print("No Response")
}
}).resume()
}) {
Text("Login")
.padding(10)
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 22))
.foregroundColor(Color(UIColor(hexString: "#006622")))
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(Color.black, lineWidth: 1))
}.padding([.top],40)
if self.status == 0 {
Text(self.message)
.foregroundColor(.red)
.font(.system(size: 20))
.padding([.top],30)
}
Spacer()
}.padding()
}
}
}
}
Try this (scratchy - not tested because not compilable due to absent dependencies), so adapt at your side:
struct ContentView: View {
#State var email: String = ""
#State var password: String = ""
#State var message: String = ""
#State var status: Int = -10
#State var showActive = false // << seems this state, so false
var body: some View {
NavigationView {
ZStack {
Color(UIColor.systemGroupedBackground)
.edgesIgnoringSafeArea(.all)
VStack(alignment: .center) {
Spacer()
TextField("Email", text: $email)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
SecureField("Password", text: $password)
.padding(10)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(Color(UIColor(hexString: ForestGreen)))
.foregroundColor(Color.black)
Button(action: {
guard let url = URL(string:ConnectionString+"profile/login") else { return }
let parameter = "email=\(self.email)&password=\(self.password)"
let request = RequestObject(AddToken: true, Url: url, Parameter: parameter)
URLSession.shared.dataTask(with:request, completionHandler: {(data, response, error) in
if let decode = try? JSONDecoder().decode(ProfileCodable.self, from: data!)
{
self.status = decode.status ?? -10
self.message = decode.message ?? ""
if decode.status == 0 {
print("Invalid Credentials")
} else if decode.status == 1 {
self.showActive = true // << here !!
} else if decode.status == -1 {
print("Error")
}
} else {
print("No Response")
}
}).resume()
}) {
Text("Login")
.padding(10)
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 22))
.foregroundColor(Color(UIColor(hexString: "#006622")))
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(Color.black, lineWidth: 1))
}.padding([.top],40)
.background(
// activated by state programmatically !!
NavigationLink(destination: MainView(), isActive: $self.showActive) {
EmptyView() // << just hide
}.buttonStyle(PlainButtonStyle())
)
if self.status == 0 {
Text(self.message)
.foregroundColor(.red)
.font(.system(size: 20))
.padding([.top],30)
}
Spacer()
}.padding()
}
}
}
}
Simply use the isActive property of navigation link. It would look like this:
NavigationLink(destination: MainView(), isActive: $mainViewActive) {
Text("Press on me")
}.buttonStyle(PlainButtonStyle())
and you should also declare the variable in your view:
#State var mainViewActive = false
then on successful login simply change the value to true. If you also do not want to display an actual link use EmptyView() as wrapper. So it would look like this:
NavigationLink(destination: MainView(), isActive: $mainViewActive) {
EmptyView()
}

SwiftUI: Generic parameter 'C0' could not be inferred

I've been working with this code and I keep getting this error: ''Generic parameter 'C0' could not be inferred'' Additionally it says 'In call to function 'buildBlock' (SwiftUI.ViewBuilder)'on my HStack when I include this line of code:
self.userData.tempBatchUnit = productName
I am not sure why. The code works fine without that line of code. Many thanks
struct enterProductUnitView: View {
#EnvironmentObject var userData: UserData
#State var productName: String = ""
var body: some View {
VStack {
HStack { // error Generic parameter 'C0' could not be inferred
Text("Product Unit:")
.font(.headline)
Spacer()
NavigationLink(destination: InstructionsView(desireInstructions: "Product Unit")) {
Text("?")
}
}
Text("ex: bags of popcorn, jars of jam etc.")
.font(.subheadline)
TextField("Enter here", text: $productName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.padding(.leading)
self.userData.tempBatchUnit = productName
}
}
}
Remove the following line - it is not allowed in bodyview builder
self.userData.tempBatchUnit = productName
I assume it should be in .onCommit
TextField("Enter here", text: $productName, onCommit: {
self.userData.tempBatchUnit = self.productName
})
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.padding(.leading)

Resources