Textfield placeholder doesn't shows up well, SwitUI - swift

I'm trying to add login form to white color view but textfield placeholder can't see well, however on preview it shows up.
I tried to change placeholder color SwiftUI. How to change the placeholder color of the TextField? but it still doesn't shows up well. Could you give me some tip how can I solve this problem?
struct CardView: View {
var body: some View {
ZStack {
Rectangle()
.fill(Color(UIColor.white))
.frame(height:300)
.cornerRadius(10)
.padding(16)
LoginForm()
}
}
}
struct LoginForm: View {
#State var username: String = ""
var body: some View {
VStack(alignment:.center) {
UsernameTextField(userNumber: $username)
.padding(50)
LoginButton()
}.padding()
}
}
struct UsernameTextField: View {
#Binding var userNumber: String
var body: some View {
TextField("Phone number", text: $userNumber)
.padding(50)
.onChange(of: userNumber, perform: { value in
userNumber = formatNumberTextField(pattern: "+X(XXX) XXX XX XX", phoneNumber: userNumber)
})
.frame(height: 48)
//.textFieldStyle(DefaultTextFieldStyle())
.cornerRadius(16)
.foregroundColor(.black)
.accentColor(.black)
.fixedSize(horizontal: true, vertical: false)
.padding([.leading, .trailing], 10)
.underlineTextField()
}

Related

How do I programmatically set secure text field and normal text field in swiftUI

SwiftUI has two different forms of text fields, one is SecureField which hides input and TextField which doesn't hide input. Instead of creating two separate views, is there a way to create a single view that takes in a parameter to create both types while repeating as little code as possible?
You just make a View with all the code you want for the SecureTextField and the TextField then all you have to do is call the HybridTextField where ever you need it.
import SwiftUI
struct HybridTextFieldUsageView: View {
#State var password: String = "password"
var body: some View {
//Use this anywhere in your code
HybridTextField(text: $password, titleKey: "password")
}
}
///Contains all the code for the Secure and regular TextFields
struct HybridTextField: View {
#Binding var text: String
#State var isSecure: Bool = true
var titleKey: String
var body: some View {
HStack{
Group{
if isSecure{
SecureField(titleKey, text: $text)
}else{
TextField(titleKey, text: $text)
}
}.textFieldStyle(.roundedBorder)
.animation(.easeInOut(duration: 0.2), value: isSecure)
//Add any common modifiers here so they dont have to be repeated for each Field
Button(action: {
isSecure.toggle()
}, label: {
Image(systemName: !isSecure ? "eye.slash" : "eye" )
})
}//Add any modifiers shared by the Button and the Fields here
}
}
struct HybridTextField_Previews: PreviewProvider {
static var previews: some View {
HybridTextFieldUsageView()
}
}
I create a custom view for PasswordTextField. May be this code will help. I don't know either it helps you, though it fulfilled my requirement. That's why sharing it to you. This is the output of my code
struct PasswordTextField: View {
#Binding var isPasswordVisible: Bool
var hint: String
#Binding var text: String
var isTextChanged: (Bool) -> Void
var body: some View {
HStack {
if isPasswordVisible {
TextFieldView(
hint: hint,
text: $text,
isTextChanged: isTextChanged
)
} else {
SecuredTextFieldView(
hint: hint,
text: $text
)
}
}.overlay(alignment: .trailing) {
Image(systemName: isPasswordVisible ? "eye.fill" : "eye.slash.fill")
.padding()
.onTapGesture {
isPasswordVisible.toggle()
}
}
}
}
struct TextFieldView: View {
var hint: String
#Binding var text: String
var isTextChanged: (Bool) -> Void
var body: some View {
TextField(
hint,
text: $text,
onEditingChanged: isTextChanged
)
.padding()
.overlay(
Rectangle().strokeBorder(
.gray.opacity(0.2),
style: StrokeStyle(lineWidth: 2.0)
)
)
}
}
struct SecuredTextFieldView: View {
var hint: String
#Binding var text: String
var body: some View {
SecureField(
hint,
text: $text
)
.padding()
.overlay(
Rectangle().strokeBorder(
.gray.opacity(0.2),
style: StrokeStyle(lineWidth: 2.0)
)
)
}
}
and call the custom view in your actual view
struct PasswordView: View {
#State var password: String = ""
#State var confirmPassword: String = ""
#State var isPasswordVisible: Bool = false
#State var isConfirmPasswordVisible: Bool = false
var body: some View {
VStack(alignment: .leading, spacing: 15) {
Text("New Password")
.font(.headline)
.fontWeight(.regular)
.padding(.top, 30)
PasswordTextField(
isPasswordVisible: $isPasswordVisible,
hint: "Password having 8 charecture",
text: $password,
isTextChanged: { (changed) in
}
)
Text("Confirm New Password")
.font(.headline)
.fontWeight(.regular)
.padding(.top, 10)
PasswordTextField(
isPasswordVisible: $isConfirmPasswordVisible,
hint: "Password having 8 charecture",
text: $confirmPassword,
isTextChanged: { (changed) in
}
)
Spacer()
}.padding(.horizontal, 25)
}
}
In your view's body you can use a ternary to create the right textfield as needed without using a giant if/else block:
(self.isSecure ? AnyView(SecureField(placeholder, text: $value)) : AnyView(TextField(placeholder, text: $value)))
This will return a view that you can use operators on, which is useful if you're creating a custom text input. For example, the following would be painful if we had to do it twice for each kind of text field. Using a ternary in the actual view body keeps you from having two giant if/else blocks.
VStack {
ZStack(alignment: .leading) {
Text(placeholder)
.foregroundColor(Color(.placeholderText))
.offset(y: $value.wrappedValue.isEmpty ? 0 : -25)
.scaleEffect($value.wrappedValue.isEmpty ? 1 : 0.8, anchor: .leading)
(self.isSecure ? AnyView(SecureField(placeholder, text: $value)) : AnyView(TextField(placeholder, text: $value)))
.onChange(of: self.value) { newValue in
if self.onChange(newValue) != true {
self.value = previousValue
}
DispatchQueue.main.async {
self.previousValue = newValue
}
}
}
.padding(.top, 15)
.animation(.easeInOut(duration: 0.2))
Divider()
.frame(height: 1)
.padding(.horizontal, 30)
.background(Color.black)
}

NavigationLink keeps aligning my text elements to center instead of leading SwiftUI

I have a CustomSearchBar view that looks like this
However, when I wrap it with NavigationLink, the placeholder text will be centered. And user inputs will be centered too.
How do I maintain the leading alignment while using NavigationLink?
My code structure looks like this:
enum Tab {
case social
}
struct MainAppView: View {
#State var selection: Tab = .social
var body: some View {
TabView(selection: $selection) {
ZStack{
CustomButton()
NavigationView { SocialView() }
}.tabItem{Image(systemName: "person.2")}.tag(Tab.social)
// other tabs....
}
struct SocialView: View {
// ...
var body: some View {
GeometryReader{ geometry in
VStack{
NavigationLink(destination: Text("test")) {
CustomSearchBar()
//...
}.navigationBarHidden(true)
.navigationBarTitle(Text(""))
}
}
}
}
struct CustomSearchBar: View {
var body: some View {
VStack{
HStack {
SearchBarSymbols(// some binding arguments)
CustomTextField(// some binding arguments)
CancelButton(// some binding arguments)
}
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
}
.padding(.horizontal)
}
}
struct CustomTextField: View {
var body: some View {
TextField("friend name", text: $searchText)
.frame(alignment: .leading)
.onTapGesture {
// some actions
}
.foregroundColor(Color("SearchBarSymbolColor"))
.accentColor(Color("SearchBarSymbolColor"))
.disableAutocorrection(true)
}
}
The issues with your code are:
Your navigation view contains the search field. This means that any new view that gets pushed will cover the search field.
Your search field is inside of the navigation link. There are conflicting interactions here as it effectively turns the field into a button, ie tapping the search field vs tapping the navigation link.
Solution:
Move the navigation view below the text field, so that the new view will appear without covering it. Then change the navigation link so that it is activated via a binding that gets triggered when the search field is editing:
struct SocialView: View {
#State private var text: String = ""
#State private var isActive: Bool = false
var body: some View {
GeometryReader{ geometry in
VStack {
CustomTextField(searchText: $text, isActive: $isActive)
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
NavigationView {
NavigationLink(isActive: $isActive, destination: { Text("test") }, label: { EmptyView() })
}
}
}
}
}
struct CustomTextField: View {
#Binding var searchText: String
#Binding var isActive: Bool
var body: some View {
TextField("friend name", text: $searchText) { editing in
self.isActive = editing
} onCommit: {
}
.frame(alignment: .leading)
.disableAutocorrection(true)
}
}

Multiple Bottom sheets - the content doesn't load SwiftUI

I have made a view with two possible bottom sheets. The action works, and Bottom Sheets do open. Crazy thing is they open without the view inside. I have to close the one I opened and open the other one. When I do and than come back to the first one I will see the content. The code builds without warnings:
LogInView - where the logic is:
import SwiftUI
struct LogInView: View {
#EnvironmentObject var userInfo: UserInfo
enum Action{
case resetPW, signUp
}
#State private var showSheet = false
#State private var action:Action?
var body: some View {
LoginEmailView(showSheet: $showSheet, action: $action)
.sheet(isPresented: $showSheet){
if self.action == .resetPW{
ModalResetPWView()
}else if self.action == .signUp{
ModalSignUpView()
}
}
}
}
The view from which actions come:
import SwiftUI
struct LoginEmailView: View {
#EnvironmentObject var userInfo: UserInfo
#StateObject var user:LogInViewModel = LogInViewModel()
// ----- > THERE IS BINDING
#Binding var showSheet: Bool
#Binding var action:LogInView.Action?
// ----- >
var body: some View {
VStack{
Spacer()
Image("logo")
HStack{
Text("Adres email:")
.padding(.horizontal, 10)
.font(.title)
.foregroundColor(.black)
Spacer()
}
TextField("Enter e-mail adress", text: self.$user.email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
.padding(.horizontal, 10)
.keyboardType(.emailAddress)
HStack{
Text("Password:")
.padding(.horizontal, 10)
.font(.title)
.foregroundColor(.black)
Spacer()
}
SecureField("Enter password", text: self.$user.password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
.padding(.horizontal,10)
HStack{
Spacer()
// ----- > First Bottom sheet
Button(action: {
self.action = .resetPW
self.showSheet = true
}) {
Text("Forgot Password")
}
.padding(.top, 5)
.padding(.trailing, 10)
// ----- >
}
Button(action: {
self.userInfo.isAuthenticated = .signedIn
}) {
Text("Log in")
}
.font(.title)
.padding(5)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.padding(.top, 10)
.opacity(user.isLogInComplete ? 1 : 0.7)
.disabled(!user.isLogInComplete)
// ----- > Second bottom sheet
Button(action: {
self.action = .signUp
self.showSheet = true
}) {
Text("Sign Up")
}
// ----- >
.padding(.top, 35)
Spacer()
}
}
}
The .sheet modifier will create the sheet view as soon as LogInView() is initialized. In your 'if.. else if..' statement, there is no logic to catch 'else' situations (situations where action == nil). Therefore, since action == nil on init(), the first .sheet that will present will fail your 'if..else if' and an EmptyView will present.
But don't worry! This is a common issue and can be easily solved. Here are 2 easy ways to implement methods to fix this (I prefer the 2nd method bc it's cleaner):
METHOD 1: Present a single view & change that view's content instead of switching between which view to present.
Instead of doing the 'if.. else if..' statement within the .sheet modifier, present a static view (I've called it SecondaryView ) that has a #Binding variable connected to your action. This way, when LogInView() appears, we can ensure that it will definitely render this view and then we can simply modify this view's content by changing the #Binding action.
import SwiftUI
struct LogInView: View {
enum Action{
case resetPW, signUp
}
#State private var showSheet = false
#State private var action: Action?
var body: some View {
LoginEmailView(showSheet: $showSheet, action: $action)
.sheet(isPresented: $showSheet) {
SecondaryView(action: $action)
}
}
}
struct LoginEmailView: View {
#Binding var showSheet: Bool
#Binding var action: LogInView.Action?
var body: some View {
VStack(spacing: 40 ){
Text("Forgot Password")
.onTapGesture {
action = .resetPW
showSheet.toggle()
}
Text("Sign Up")
.onTapGesture {
action = .signUp
showSheet.toggle()
}
}
}
}
struct SecondaryView: View {
#Binding var action: LogInView.Action?
var body: some View {
if action == .signUp {
Text("SIGN UP VIEW HERE")
} else {
Text("FORGOT PASSWORD VIEW HERE")
}
}
}
METHOD 2: Make each Button it's own View, so that it can have it's own .sheet modifier.
In SwiftUI, we are limited to 1 .sheet() modifier per View. However, we can always add Views within Views and each subview is then allowed it's own .sheet() modifier as well. So the easy solution is to make each of your buttons their own view. I prefer this method because we no longer need to pass around the #State/#Binding variables between views.
struct LogInView: View {
var body: some View {
LoginEmailView()
}
}
struct LoginEmailView: View {
var body: some View {
VStack(spacing: 40 ){
ForgotPasswordButton()
SignUpButton()
}
}
}
struct ForgotPasswordButton: View {
#State var showSheet: Bool = false
var body: some View {
Text("Forgot Password")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("FORGOT PASSWORD VIEW HERE")
})
}
}
struct SignUpButton: View {
#State var showSheet: Bool = false
var body: some View {
Text("Sign Up")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("SIGN UP VIEW HERE")
})
}
}

Text() in front of TextField() blocking editing in SwiftUI

So I'd like my textfield to have a customizable placeholder text so I decided to put a Text() element in a ZStack in front of the text field. The only problem is, this Text() item blocks the selection of the textfield that is behind it (AKA when I click the placeholder I want the TextField to be clicked). Unfortunately, this Text() element blocks the click. I tried using the .allowsHitTesting() property as seen below but that also didn't work, and I'm not sure why.
struct ContentView: View {
#State var text: String = ""
var body: some View {
ZStack {
TextField("", text: self.$text)
.background(Color.red)
.foregroundColor(Color.white)
if text.isEmpty {
Text("Placeholder")
.allowsHitTesting(false)
}
}
}
}
It can be done with custom text field style.
Here is a demo of solution (or parameters can be tuned). Tested with Xcode 12 / iOS 14 (border is just for visibility)
struct PlaceholderStyle: TextFieldStyle {
let isActive: Bool
var placeholder = "Placeholder"
var color = Color.white
var backgrond = Color.red
func _body(configuration: TextField<_Label>) -> some View {
Text("\(isActive ? placeholder : "")")
.foregroundColor(isActive ? color : .clear)
.background(isActive ? backgrond : .clear)
.frame(maxWidth: .infinity, alignment: .leading)
.overlay(configuration)
}
}
struct DemoView: View {
#State private var text = ""
var body: some View {
TextField("", text: $text)
.border(Color.gray).padding(.horizontal)
.textFieldStyle(PlaceholderStyle(isActive: text.isEmpty))
}
}
See if this fits your needs:
struct ContentView: View {
#State var text = ""
var body: some View {
ZStack(alignment: .leading) {
if text.isEmpty { Text("Placeholder")
.foregroundColor(.red)
.background(Color.yellow)
}
TextField("", text: $text)
.background(text.isEmpty ? Color.clear : Color.yellow)
}
}
}

How to make a button clickable to another view controller only with textfield

I have this scenario that if the user clicks the button then another view controller will popuped which includes only a textView where the user can write something inside.
You can use the the .sheet modifier to display a modal / popup like view in SwiftUI.
struct ContentDetail: View {
struct Item {
let uuid = UUID()
let value: String
}
#State private var items = [Item]()
#State private var show_modal = false
var lectureName:String
var body: some View {
ZStack {
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
self.show_modal.toggle()
}, label: {
Text("✏️")
.font(.system(.largeTitle))
.frame(width: 77, height: 70)
.foregroundColor(Color.white)
.padding(.bottom, 7)
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 5,
x: 3,
y: 3)
}
}
}
.sheet(isPresented: self.$show_modal) {
CustomModalView()
}
}
}
struct CustomModalView: View {
#State private var text = ""
var body: some View {
TextField("test", text: $text)
.padding(5)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 60, design: .default))
.multilineTextAlignment(.center)
}
}
You can read more about it here:
https://blog.appsbymw.com/posts/how-to-present-and-dismiss-a-modal-in-swiftui-155c/