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.
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.