Is there an alternative for textFieldLeader in SwiftUI's SecureField? - ios

As the title says, I'm trying to add some leading padding if the user provides a symbol for the icon. This works just fine in the TextField View but a SecureField doesn't have this as an option. Is there an alternative that I'm missing?
struct PasswordInputView: View {
#Binding var password: String
let placeholder: String
let sfSymbol: String?
var body: some View {
SecureField(placeholder, text: $password)
.frame(maxWidth: .infinity,
minHeight: 44)
.padding(.leading, sfSymbol == nil ? textFieldLeader / 2 : textFieldLeader) <---- ERROR: Cannot find textFieldLeader
.background(
ZStack {
if let systemName = sfSymbol {
Image(systemName: systemName)
.font(.system(size: 16, weight: .semibold))
.padding(.leading, 5)
.foregroundColor(Color.gray.opacity(0.5))
}
RoundedRectangle(cornerRadius: 10, style: .continuous)
.stroke(Color.gray.opacity(0.25))
}
)
}
}

Related

Can't convert String to Int in #AppStorage

I want to convert all #AppStorage's to Int(or even to Float) value and after that multiply it. Or probably i should to convert salaryPh and hoursPm to Int values, but i also can't do it. I've tried to create function where i could change String to Int but it didn't help me
I think that i'm doing something wrong, tried to find the solution with same question and found only one, but it didn't help me(link)
import SwiftUI
struct HomeView: View {
var body: some View {
NavigationView {
ExtractedView()
}
}
}
struct ExtractedView: View {
#State var monthString = Date().getMonthString().lowercased().capitalized
#State private var salaryPh: String = ""
#State private var hoursPm: String = ""
#AppStorage("SALARY_KEY") var savedSalary = ""
#AppStorage("HOURS_KEY") var savedHoursPm = ""
var body: some View {
ZStack {
BackgroundView()
CalendarView()
RoundedRectangle(cornerRadius: 40)
.frame(width: 225, height: 100)
.foregroundColor(.blue)
.offset(y: 190)
VStack(alignment: .center, spacing: 13) {
Text("Your netto-salary per hour")
.foregroundColor(Color.white)
.font(Font.system(size: 23, design: .rounded))
.fontWeight(.light)
TextField("Salary", text: $salaryPh)
.frame(width: 100, height: 50)
.background(.black)
.opacity(0.5)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.font(.system(.body, design: .monospaced))
.overlay (
RoundedRectangle(cornerRadius: 15)
.stroke(Color.blue, lineWidth: 4)
)
.onChange(of: salaryPh) { salaryPh in
self.savedSalary = salaryPh
}
.onAppear {
self.salaryPh = savedSalary
print("Loaded: \(savedSalary)")
}
}
.offset(y: -150)
VStack(alignment: .center, spacing: 13) {
Text("Hours in this month")
.foregroundColor(Color.white)
.font(Font.system(size: 23, design: .rounded))
.fontWeight(.light)
TextField("Hours", text: $hoursPm)
.foregroundColor(Color.white)
.frame(width: 100, height: 50)
.background(.black)
.opacity(0.5)
.multilineTextAlignment(.center)
.font(.system(.body, design: .monospaced))
.overlay (
RoundedRectangle(cornerRadius: 15)
.stroke(Color.blue, lineWidth: 4)
)
.onChange(of: hoursPm) { hoursPm in
self.savedHoursPm = hoursPm
}
.onAppear {
self.hoursPm = savedHoursPm
print("Loaded: \(savedHoursPm)")
}
}
.offset(y: -20)
VStack(spacing: 20) {
Text("In \(monthString) i make:")
.foregroundColor(Color.white)
.font(Font.system(size: 23, design: .rounded))
.fontWeight(.light)
}
.offset(y: 165)
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
You can get rid of all the conversion by using TextField with value.
TextField(
"Double",
value: $myDouble,
format: .number
)
This setup will work with Int too.
Once the TextField is compatible with numbers you can switch all the Strings to be the correct number type.
https://developer.apple.com/documentation/swiftui/textfield/init(_:value:format:prompt:)-3fh51

TextField stroke border text with SwiftUI

Here is what I've done, but the problem is with Text background. It can be implemented on white background by setting the Text's background to white as well, but in case of image background it stays "strikedthrough". You can find a source code below where I tried to make it as close to the result as possible. How it could be resolved?
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
TextField("", text: $text)
.placeholder(when: $text.wrappedValue.isEmpty,
alignment: .leading,
placeholder: {
Text(placeholder)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(.leading, 15)
})
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.gray, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.padding(2)
.font(.caption)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
}
}
Here is a solution using .trim on two RoundedRectangles based on the length of the label text, which should give you the result you want:
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
#State private var width = CGFloat.zero
#State private var labelWidth = CGFloat.zero
var body: some View {
TextField(placeholder, text: $text)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.trim(from: 0, to: 0.55)
.stroke(.gray, lineWidth: 1)
RoundedRectangle(cornerRadius: 5)
.trim(from: 0.565 + (0.44 * (labelWidth / width)), to: 1)
.stroke(.gray, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.overlay( GeometryReader { geo in Color.clear.onAppear { labelWidth = geo.size.width }})
.padding(2)
.font(.caption)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
.overlay( GeometryReader { geo in Color.clear.onAppear { width = geo.size.width }})
.onChange(of: width) { _ in
print("Width: ", width)
}
.onChange(of: labelWidth) { _ in
print("labelWidth: ", labelWidth)
}
}
}
Here is my version of the TextField.
struct TextInputField: View {
let placeHolder: String
#Binding var textValue: String
var body: some View {
ZStack(alignment: .leading) {
Text(placeHolder)
.foregroundColor(Color(.placeholderText))
.offset(y: textValue.isEmpty ? 0 : -25)
.scaleEffect(textValue.isEmpty ? 1: 0.8, anchor: .leading)
TextField("", text: $textValue)
}
.padding(.top, textValue.isEmpty ? 0 : 15)
.frame(height: 52)
.padding(.horizontal, 16)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(lineWidth: 1).foregroundColor(.gray))
.animation(.default)
}
}
The above code is to create a CustomTextField named TextInputField. If you want to use the about component
struct ContentView: View {
#State var itemName: String = ""
var body: some View {
TextInputField(placeHolder: "Item Name": textValue: $itemName)
}
}
I'm using #ChrisR's answer as a base for my answer, so instead of doing all that calculation with two RoundedRectangles and label's width; you can position the Text on top and give it a background matching the app's background color
struct FloatingTitleTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
TextField("Placeholder", text: $text)
.foregroundColor(.gray)
.font(.system(size: 20))
.padding(EdgeInsets(top: 15, leading: 10, bottom: 15, trailing: 10))
.background {
ZStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.black, lineWidth: 1)
Text(placeholder)
.foregroundColor(.gray)
.padding(2)
.background()
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .topLeading)
.offset(x: 20, y: -10)
}
}
}
}
When calling the textfield you do it like this
FloatingTitleTextField(placeholder: "Placeholder", text: $text)
I also found this article very helpful

SwiftUI: Padding inside TextField

I have a TextField in SwiftUI. When I apply padding to it as
TextField(text, text: $value)
.padding()
the padding is outside of the tap area of the TextField, i.e. tapping on the padding does not bring the text field into focus.
I would like to be able to focus the TextField even if the user taps on the padding.
You can try this. At least worked for me. Hope that helps someone:
TextField("", text: $textfieldValueBinding)
.frame(height: 48)
.padding(EdgeInsets(top: 0, leading: 6, bottom: 0, trailing: 6))
.cornerRadius(5)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(lineWidth: 1.0)
)
you can check this. for now i only did with top and bottom padding. you can do the same with the leading and trailing(or with horizontal and vertical).
as asked in the question this would do this
"I would like to be able to focus the TextField even if the user taps on the padding."
struct ContentView: View {
#State var name = ""
#State var isTextFieldFocused = false
var body: some View {
ZStack {
HStack{
Text(name)
.font(.system(size: 50 , weight: .black))
.foregroundColor(isTextFieldFocused ? Color.clear : Color.black)
Spacer()
}
TextField(name, text: $name , onEditingChanged: { editingChanged in
isTextFieldFocused = editingChanged
})
.font(.system(size: isTextFieldFocused ? 50 : 100 , weight: .black))
.foregroundColor(isTextFieldFocused ? Color.black : Color.clear)
.frame(width: 300, height: isTextFieldFocused ? 50 : 100 , alignment: .center)
.padding(.leading, isTextFieldFocused ? 25 : 0 )
.padding(.trailing, isTextFieldFocused ? 25 : 0 )
.padding(.top,isTextFieldFocused ? 25 : 0 )
.padding(.bottom,isTextFieldFocused ? 25 : 0 )
}.frame(width: 300)
.background(Color.red.opacity(0.2))
}
}
Yes, but you have to create your own TextFieldStyle. Here's an example:
struct CustomTextField: View {
public struct CustomTextFieldStyle : TextFieldStyle {
public func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.font(.largeTitle) // set the inner Text Field Font
.padding(10) // Set the inner Text Field Padding
//Give it some style
.background(
RoundedRectangle(cornerRadius: 5)
.strokeBorder(Color.primary.opacity(0.5), lineWidth: 3))
}
}
#State private var password = ""
#State private var username = ""
var body: some View {
VStack {
TextField("Test", text: $username)
.textFieldStyle(CustomTextFieldStyle()) // call the CustomTextField
SecureField("Password", text: $password)
.textFieldStyle(CustomTextFieldStyle())
}.padding()
}
}
For iOS 15 and above you can use this:
var body: some View {
TextField("placeholder", text: $text).focusablePadding()
}
extension View {
func focusablePadding(_ edges: Edge.Set = .all, _ size: CGFloat? = nil) -> some View {
modifier(FocusablePadding(edges, size))
}
}
private struct FocusablePadding : ViewModifier {
private let edges: Edge.Set
private let size: CGFloat?
#FocusState private var focused: Bool
init(_ edges: Edge.Set, _ size: CGFloat?) {
self.edges = edges
self.size = size
self.focused = false
}
func body(content: Content) -> some View {
content
.focused($focused)
.padding(edges, size)
.contentShape(Rectangle())
.onTapGesture { focused = true }
}
}
TextField(
"TextFiled",
text: $teamNames,
prompt: Text("Text Field")
.foregroundColor(.gray)
)
.padding(5)
.frame(width: 250, height: 50, alignment: .center)
.background(
RoundedRectangle(cornerRadius: 25, style: .continuous)
.foregroundColor(.white)
.padding(.horizontal, -30)
)
I don't know if you can give padding inside of a TextField, but you can pad it from outside environment and give it a background of a shape with the same color of your TextField background.
Try this;
TextField("Username", text: $value)
.background(Color.yellow)
.padding(50)
.background(Capsule().fill(Color.white))

SwiftUI - Button with Image is clickable outside

I have a ScrollView with multiple Buttons. A Button contains a Image and a Text underneath.
As the images are pretty large I am using .scaledToFill and .clipped. And it seems that the 'clipped' part of the image is still clickable even if it's not shown.
In the video you see I am clicking on button 1 but button 2 is triggered.
This is my Coding. The Image is inside the View Card.
struct ContentView: View {
#State var useWebImage = false
#State var isSheetShowing = false
#State var selectedIndex = 0
private let images = [
"https://images.unsplash.com/photo-1478368499690-1316c519df07?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2706&q=80",
"https://images.unsplash.com/photo-1507154258-c81e5cca5931?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2600&q=80",
"https://images.unsplash.com/photo-1513310719763-d43889d6fc95?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2734&q=80",
"https://images.unsplash.com/photo-1585766765962-28aa4c7d719c?ixlib=rb-1.2.1&auto=format&fit=crop&w=2734&q=80",
"https://images.unsplash.com/photo-1485970671356-ff9156bd4a98?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2734&q=80",
"https://images.unsplash.com/photo-1585607666104-4d5b201d6d8c?ixlib=rb-1.2.1&auto=format&fit=crop&w=2700&q=80",
"https://images.unsplash.com/photo-1577702066866-6c8897d06443?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2177&q=80",
"https://images.unsplash.com/photo-1513809491260-0e192158ae44?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2736&q=80",
"https://images.unsplash.com/photo-1582092723055-ad941d1db0d4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2700&q=80",
"https://images.unsplash.com/photo-1478264635837-66efba4b74ba?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjF9&auto=format&fit=crop&w=2682&q=80"
]
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 40) {
Text(useWebImage ? "WebImage is used." : "SwiftUI Image is used")
.font(.system(size: 18))
.bold()
.kerning(0.5)
.padding(.top, 20)
Toggle(isOn: $useWebImage) {
Text("Use WebImage")
.font(.system(size: 18))
.bold()
.kerning(0.5)
.padding(.top, 20)
}
ForEach(0..<images.count) { index in
Button(action: {
self.selectedIndex = index
self.isSheetShowing.toggle()
}) {
Card(imageUrl: self.images[index], index: index, useWebImage: self.$useWebImage)
}
.buttonStyle(PlainButtonStyle())
}
}
.padding(.horizontal, 20)
.sheet(isPresented: self.$isSheetShowing) {
DestinationView(imageUrl: self.images[self.selectedIndex], index: self.selectedIndex, useWebImage: self.$useWebImage)
}
}
.navigationBarTitle("Images")
}
}
}
struct Card: View {
let imageUrl: String
let index: Int
#Binding var useWebImage: Bool
var body: some View {
VStack {
if useWebImage {
WebImage(url: URL(string: imageUrl))
.resizable()
.indicator(.activity)
.animation(.easeInOut(duration: 0.5))
.transition(.fade)
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
.cornerRadius(12)
.clipped()
} else {
Image("image\(index)")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
.cornerRadius(12)
.clipped()
}
HStack {
Text("Image #\(index + 1) (\(useWebImage ? "WebImage" : "SwiftUI Image"))")
.font(.system(size: 18))
.bold()
.kerning(0.5)
Spacer()
}
}
.padding(2)
.border(Color(.systemRed), width: 2)
}
}
Do you have an idea how to fix this issue?
I already tried to use .resizable(resizingMode: .tile) but I need to shrink the image before I could use just a tile.
For detailed information you can also find the project on GitHub GitHub Project
I would appreciate your help a lot.
The .clipped affects only drawing, and by-default Button has all content clickable not depending what it is.
So if you want make your button clickable only in image area, you have to limit hit testing only to its rect explicitly and disable everything else.
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4.
Demo code (simplified variant of your snapshot):
struct ButtonCard: View {
var body: some View {
VStack {
Image("sea")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
.cornerRadius(12)
.contentShape(Rectangle()) // << define clickable rect !!
.clipped()
HStack {
Text("Image #1")
.font(.system(size: 18))
.bold()
.kerning(0.5)
Spacer()
}.allowsHitTesting(false) // << disable label area !!
}
.padding(2)
.border(Color(.systemRed), width: 2)
}
}
struct TestClippedButton: View {
var body: some View {
Button(action: { print(">> tapped") }) {
ButtonCard()
}.buttonStyle(PlainButtonStyle())
}
}

Incorrect Spacing in SwiftUI View

Xcode 11.2.1, Swift 5
I have a custom view in SwiftUI and I have been fiddling around with it for a while to try and get rid of the extra space being added. I cannot tell if this is a bug in SwiftUI itself or if I am doing something wrong.
View:
struct Field: View {
#Binding var text: String
var title: String
var placeholderText: String
var leftIcon: Image?
var rightIcon: Image?
var onEditingChanged: (Bool) -> Void = { _ in }
var onCommit: () -> Void = { }
private let height: CGFloat = 47
private let iconWidth: CGFloat = 16
private let iconHeight: CGFloat = 16
init(text: Binding<String>, title: String, placeholder: String, leftIcon: Image? = nil, rightIcon: Image? = nil, onEditingChanged: #escaping (Bool) -> Void = { _ in }, onCommit: #escaping () -> Void = { }) {
self._text = text
self.title = title
self.placeholderText = placeholder
self.leftIcon = leftIcon
self.rightIcon = rightIcon
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit
}
var body: some View {
VStack(alignment: .leading, spacing: 3) {
Text(title.uppercased())
.font(.caption)
.fontWeight(.medium)
.foregroundColor(.primary)
.blendMode(.overlay)
Rectangle()
.fill(Color(red: 0.173, green: 0.173, blue: 0.180))
.blendMode(.overlay)
.cornerRadius(9)
.frame(height: height)
.overlay(
HStack(spacing: 16) {
if leftIcon != nil {
leftIcon!
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: iconWidth, height: iconHeight)
.foregroundColor(.secondary)
}
ZStack(alignment: .leading) {
if text.isEmpty {
Text(placeholderText)
.foregroundColor(.secondary)
.lineLimit(1)
.truncationMode(.tail)
}
TextField("", text: $text, onEditingChanged: onEditingChanged, onCommit: onCommit)
.foregroundColor(.white)
.lineLimit(1)
.truncationMode(.tail)
}
Spacer()
if rightIcon != nil {
rightIcon!
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: iconWidth, height: iconHeight)
.foregroundColor(.secondary)
}
}
.padding(.horizontal, 16)
)
}
}
}
When I create an instance of it like so:
struct ContentView: View {
#State var text = ""
let backgroundGradient = Gradient(colors: [
Color(red: 0.082, green: 0.133, blue: 0.255),
Color(red: 0.227, green: 0.110, blue: 0.357)
])
var body: some View {
ZStack {
LinearGradient(gradient: backgroundGradient, startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
Field(text: $text, title: "field", placeholder: "Required", leftIcon: Image(systemName: "square.and.pencil"), rightIcon: Image(systemName: "info.circle"))
.padding(.horizontal, 30)
}
.onAppear {
UIApplication.shared.windows.forEach { window in
window.overrideUserInterfaceStyle = .dark
}
}
}
}
At first glance, it seems to behave as expected:
The problem appears after the user tries typing in the field. There is too much spacing between the right icon and the text, causing it to be truncated earlier than necessary.
As you can see, there is a much larger amount of spacing between the text field and the right icon (excess spacing) than there is between the text field and the left icon (correct spacing).
All elements of Field should have a spacing of 16 on all sides. For some reason, the TextField is not taking up enough space and thus its spacing from the right icon is less than the desired 16.
How can I fix this spacing?
You have the following:
Spacer()
before the following section of code:
if rightIcon != nil {
rightIcon!
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: iconWidth, height: iconHeight)
.foregroundColor(.secondary)
}
Removing the Spacer() will give you the same pixels between the left icon and the right icon.
*SwiftUI
///For vertical Custom Spaces
.padding(.vertical, 6)
//For Horizontal
.padding(. horizontal, 6)
//For uniform Spacting
Spacer()
///

Resources