Setting a boolean in swiftui with if statement - swiftui

I'm very new to swiftui so I don't know if I'm doing completely wrong but the error is not helpful. Here is my code:
import SwiftUI
struct IntroScreenP2: View {
var hasName: Bool = false
#State private var firstName: String = ""
#State private var lastName: String = ""
var body: some View {
VStack {
if firstName != "" && lastName != "" {
hasName = true
}
TextField("First Name", text: $firstName)
.padding(.leading)
.padding(.bottom, 5)
Divider()
TextField("Last Name", text: $lastName)
.padding(.leading)
.padding(.top, 5)
}
}
}
I'm just trying to set a boolean equal to true when the user enters their name in the text field. It keeps giving me an error saying "Type of expression is ambiguous without more context"

you cannot just "code" as usual in SwiftUI. SwiftUI requires that you return Views.
Maybe you should read some Introduction and Tutorials to SwiftUI.
Here is the code "corrected". If you tell me what you want with your variable i can help you further more.
struct ContentView: View {
#State var hasName: Bool = false
#State private var firstName: String = ""
#State private var lastName: String = ""
var body: some View {
VStack {
if firstName != "" && lastName != "" {
Text("Has name")
}
TextField("First Name", text: $firstName)
.padding(.leading)
.padding(.bottom, 5)
Divider()
TextField("Last Name", text: $lastName)
.padding(.leading)
.padding(.top, 5)
}
}
}

You don't need to set it at all! Just make it computed:
var hasName: Bool { firstName != "" && lastName != "" }
Note that it's better to use .isEmpty instead:
var hasName: Bool { !firstName.isEmpty && !lastName.isEmpty }

Related

Picker view is not changing when you try to select a different option in swiftui

I am using in swiftUI. When select picker, it is not changing. Here is code..
Here is datamodel:
struct SourceAccountModel:Codable,Identifiable{
var id: Int
let accountNumber: String
let accountTitle: String
let priaryAccount: String
init(id:Int=0,accountNumber: String, accountTitle: String, priaryAccount: String) {
self.id = id
self.accountNumber = accountNumber
self.accountTitle = accountTitle
self.priaryAccount = priaryAccount
}
}
Here is my code
struct Test2: View {
#State private var selectedOption = "Option 1"
#State private var sourceAccountList = [SourceAccountModel]()
var body: some View {
VStack{
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(Color.white)
.shadow(radius: 2)
Picker(selection: $selectedOption,label: EmptyView()) {
ForEach (0..<sourceAccountList.count,id: \.self) {
Text(sourceAccountList[$0].accountNumber)
}
}
.padding(8)
}
.frame(maxWidth: .infinity)
}.onAppear{
intitializeValue()
}
}
func intitializeValue(){
self.sourceAccountList.append(SourceAccountModel(id:1,accountNumber: "Option 1", accountTitle: "", priaryAccount: ""))
self.sourceAccountList.append(SourceAccountModel(id:2,accountNumber: "Option 2", accountTitle: "", priaryAccount: ""))
}
}
Always select first value. What is the wrong with my code?
selectedOption is a String, but your ForEach iterates over Range<Int>.
You can fix this by changing selectedOption to Int, e.g.
#State private var selectedOption = 0
You might find it easier to store the actual object in selectedOption: SourceAccountModel, iterate over the sourceAccountList, and tag each row:
struct SourceAccountModel: Identifiable, Hashable {
let id: Int
let accountNumber: String
init(id: Int, accountNumber: String) {
self.id = id
self.accountNumber = accountNumber
}
}
struct ContentView: View {
init() {
let sourceAccountList = [SourceAccountModel(id: 1, accountNumber: "Option 1"),
SourceAccountModel(id: 2, accountNumber: "Option 2")]
_sourceAccountList = State(wrappedValue: sourceAccountList)
_selectedOption = State(wrappedValue: sourceAccountList[0])
}
#State private var selectedOption: SourceAccountModel
#State private var sourceAccountList = [SourceAccountModel]()
var body: some View {
VStack {
Picker("Select", selection: $selectedOption) {
ForEach(sourceAccountList) { model in
Text(model.accountNumber).tag(model)
}
}
}
}
}

Passing down #FocusState to another view

I was wondering how would u be able to pass down a #FocusState to another view. Here is some example code.
struct View1: View {
enum Field {
case username, password
}
#State var passwordText: String = ""
#FocusState var focusedField: Field?
var body: some View {
// How would I be able to pass the focusedField here?
View2(text: $passwordText, placeholder: "Password")
//TextField("Password", text: $passwordText)
//.frame(minHeight: 44)
//.padding(.leading, 8)
//.focused($focusedField, equals: .password)
// How would I be able to add the commented code above to View2
}
}
struct View2: View {
#Binding var text: String
let placeholder: String
var body: some View {
HStack {
TextField(placeholder, text: $text)
.frame(minHeight: 44)
.padding(.leading, 8)
// How would I be able to add this
//.focused(binding: , equals: )
if text.count > 0 {
Image(systemName: "xmark.circle.fill")
.font(.headline)
.foregroundColor(.secondary)
.padding(.trailing, 8)
}
}
}
}
How would I be able to pass it down to View2. Or is there a better way to reuse a custom textfield? Would appreciate any help.
You can pass its binding as argument, like
struct View1: View {
enum Field {
case username, password
}
#State var passwordText: String = ""
#FocusState var focusedField: Field?
var body: some View {
View2(text: $passwordText, placeholder: "Password", focused: $focusedField)
}
}
struct View2: View {
#Binding var text: String
let placeholder: String
var focused: FocusState<View1.Field?>.Binding // << here !!
var body: some View {
HStack {
TextField(placeholder, text: $text)
.frame(minHeight: 44)
.padding(.leading, 8)
.focused(focused, equals: .password) // << here !!
if text.count > 0 {
Image(systemName: "xmark.circle.fill")
.font(.headline)
.foregroundColor(.secondary)
.padding(.trailing, 8)
}
}
}
}

How to pass a var for a view.init in fullScreenCover?

I have a view presented with fullScreenCover. I pass a bunch of variables, all good but ONE of them (input) is not passing/updating. I've noticed the value always retains the first input var from the first fullScreenCover call from the parent view (I call fullscreencover multiple times)
How to pass a var for a view.init in fullScreenCover?
full screen initialization
.fullScreenCover(isPresented: $likeModal, content: {
LikeScreenModalView.init(likeModalShown: $likeModal, indexHere: $index, input: model.matches[index].imageUrl1!, receiver: model.matches[index].name, type: "Image", question: "", receiverImage: model.matches[index].imageUrl1!, picNumber: 1)
.environmentObject(ChatsViewModel())
.environmentObject(ContentModel())
})
struct LikeScreenModalView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var chatModel: ChatsViewModel
#EnvironmentObject var model: ContentModel
#Binding var likeModalShown : Bool
#Binding var indexHere: Int
#State var opener: String = ""
#State public var input : String
var receiver: String
var type: String
var question: String
var receiverImage: String
var picNumber: Int
var body: some View {
VStack {
//object reference for opener
if type == "Image" {
WebImage(url: URL(string: input))
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
.padding(10)
} else {
VStack {
Text(input)
}
}
TextField("Say something nice", text: $opener).font(.title)
.multilineTextAlignment(.center)
.padding()
Button {
//write in firebase a new conversation
let user = UserService.shared.user
print(user.id)
//move to next match
DispatchQueue.main.async {
if indexHere == model.matches.count-1 {
//go back to first match
indexHere = 0
} else {
indexHere += 1
}
}
likeModalShown.toggle() //flip to false
chatModel.startConversation(receiver: receiver, message: opener, receiverImg: receiverImage)
} label: {
Text("Send Message")
}.padding()
Button("I chickened out...") {
presentationMode.wrappedValue.dismiss()
}.padding()
}
}
}
#State public var input : String
it may works

How to bind a function in view model to a custom view in swiftui?

I have a custom textfield:
struct InputField: View {
var inputText: Binding<String>
var title: String
var placeholder: String
#State var hasError = false
var body: some View {
VStack(spacing: 5.0) {
HStack {
Text(title)
.font(.subheadline)
.fontWeight(.semibold)
Spacer()
}
TextField(placeholder, text: inputText).frame(height: 50).background(Color.white)
.cornerRadius(5.0)
.border(hasError ? Color.red : Color.clear, width: 1)
}
}
}
my view model is:
class LoginViewModel: ObservableObject {
#Published var username = "" {
didSet {
print("username is: \(username)")
}
}
func checkUsernameisValid() -> Bool {
return username.count < 6
}
}
and my final login view:
#ObservedObject var loginViewModel = LoginViewModel()
var inputFields: some View {
VStack {
InputField(inputText: $loginViewModel.username, title: "Username:", placeholder: " Enter your username", hasError: $loginViewModel.checkUsernameisValid())
InputField(inputText: $loginViewModel.password, title: "Password:", placeholder: " Enter your password", hasError: $loginViewModel.checkUsernameisValid())
}
}
Now this complains at hasError:$loginViewModel.checkUsernameisValid() that I cannot bind a function to the state var hasError.
How can I make this work by still using the function checkUsernameisValid() to update my custom textfield view ?
One way I can solve this is by using another published var in my view model
#Published var validUsername = false
func checkUsernameisValid() {
validUsername = username.count < 6
}
and keep calling this function in the didSet of my username var
#Published var username = "" {
didSet {
print("username is: \(username)")
checkUsernameisValid()
}
}
finally use the new published var to bind the hasError:
hasError: $loginViewModel.validUsername
My question is, is this the only way ? i.e use #published var for binding, and I cannot use standalone functions directly to do the same thing instead of using more and more #Published variables ?
You don't need binding for error. The InputField will be updated by inputText, so you just need a regular property, like
struct InputField: View {
var inputText: Binding<String>
var title: String
var placeholder: String
var hasError = false // << here !!
// ...
}
and now pass just call
InputField(inputText: $loginViewModel.username, title: "Username:", placeholder: " Enter your username",
hasError: loginViewModel.checkUsernameisValid()) // << here !!
Tested with Xcode 12.1 / iOS 14.1
Try:
#ObservedObject var loginViewModel = LoginViewModel()
var inputFields: some View {
VStack {
InputField(inputText: $loginViewModel.username, title: "Username:", placeholder: " Enter your username", hasError: loginViewModel.checkUsernameisValid())
InputField(inputText: $loginViewModel.password, title: "Password:", placeholder: " Enter your password", hasError: loginViewModel.checkUsernameisValid())
}
}
The function works on the actual value on the bound variable, not the binding itself.

How to set the style of the textField part of a DatePicker

I used a DatePicker inside a Form, and It looks like the following image ,
Now,I hope it display "2020-4-19 " instead of "4/19/20 ".
Someone knows how to do it?
I could not find a way with the default DatePicker, so I've taken the code from
Change selected date format from DatePicker SwiftUI
made some changes to make it work. This CustomDatePicker should do you what you asked for even within a Form.
struct CustomDatePicker: View {
#State var text: String = "Date"
#Binding var date: Date
#State var formatString: String = "yyyy-MM-dd"
#State private var disble: Bool = false
#State private var showPicker: Bool = false
#State private var selectedDateText: String = "Date"
let formatter = DateFormatter()
private func setDateString() {
formatter.dateFormat = formatString
self.selectedDateText = formatter.string(from: self.date)
}
var body: some View {
VStack {
HStack {
Text(text).frame(alignment: .leading)
Spacer()
Text(self.selectedDateText)
.onAppear() {
self.setDateString()
}
.foregroundColor(.blue)
.onTapGesture {
self.showPicker.toggle()
}.multilineTextAlignment(.trailing)
}
if showPicker {
DatePicker("", selection: Binding<Date>(
get: { self.date},
set : {
self.date = $0
self.setDateString()
}), displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle())
.labelsHidden()
}
}
}
}
struct ContentView: View {
#State var date = Date()
var body: some View {
Form {
Section {
CustomDatePicker(text: "my date", date: $date, formatString: "yyyy-MM-dd")
Text("test")
}
}
}