Reputation: 1450
I'm trying to implement such component
TextField (5 000) and Text (PLN) together should be centered horizontally. On entering new digit's, Text (PLN) should be dismissed. I think I have to combine this two views in to one container and center in, something like
HStack {
TextField()
Text("PLN")
}
.frame(alignment: .center)
But TextField is taking all possible width.
How could I handle it, or probably another solution.
Upvotes: 4
Views: 3017
Reputation: 833
We can also prevent TextField
being excessed to the container by fixing the maxWidth like:
DemoCenterTextField.swift
struct DemoCenterTextField: View {
@State private var leadingContentWidth = CGFloat.zero
@State private var textFieldWidth = CGFloat.zero
@State private var trailingContentWidth = CGFloat.zero
@State private var componentWidth = CGFloat.zero
@State private var value = ""
private let minWidth = 80.0
private let spacing = 8.0
var body: some View {
HStack(spacing: spacing) {
HStack {
Text("$")
}
.getWidth($leadingContentWidth) // 👈
.background(.red.opacity(0.5))
ZStack {
Text(value)
.opacity(0)
.frame(maxWidth: max(minWidth, componentWidth - leadingContentWidth - trailingContentWidth - spacing * 2)) // 👈
.fixedSize()
.getWidth($textFieldWidth) // 👈
TextField("#.#", text: $value)
.frame(minWidth: minWidth, idealWidth: max(minWidth, textFieldWidth)) // 👈
.fixedSize(horizontal: true, vertical: false)
.border(.red.opacity(0.5))
}
.font(.system(size: 40).bold())
.printSize()
HStack {
Text("USD")
Text("!!!!!!")
}
.getWidth($trailingContentWidth) // 👈
.background(.red.opacity(0.5))
}
.frame(maxWidth: .infinity)
.getWidth($componentWidth) // 👈
.background(.red.opacity(0.2))
.multilineTextAlignment(.center)
}
}
Where .getWidth
& .printSize()
modifier are:
GetWidthModifier.Swift
import SwiftUI
public struct GetWidthModifier: ViewModifier {
@Binding var width: CGFloat
public func body(content: Content) -> some View {
content
.background(
GeometryReader { proxy in
Color.clear
.task(id: proxy.size.width) {
$width.wrappedValue = max(proxy.size.width, 0)
}
}
)
}
}
public extension View {
func getWidth(_ width: Binding<CGFloat>) -> some View {
modifier(GetWidthModifier(width: width))
}
}
struct GetWidthModifierPreview: View {
@State private var width1 = CGFloat.zero // 👈
@State private var width2 = CGFloat.zero // 👈
@State private var adjustableWidth = 202.0
var body: some View {
VStack(spacing: 16) {
Text("Width: \(width1)")
.background(.green)
.getWidth($width1) // 👈
Text("Stable Width: \(width2)")
.frame(width: adjustableWidth)
.background(.yellow)
.getWidth($width2) // 👈
Slider(value: $adjustableWidth, in: 0 ... 300)
Text("In some cases, the values obtained through GeometryReader may cause us to fall into an endless loop, resulting in unstable views and performance losses")
}
}
}
#Preview {
GetWidthModifierPreview()
}
PrintSizeModifier.swift
import SwiftUI
public struct PrintSizeModifier: ViewModifier {
@State private var size = CGSize.zero
private let alignment: Alignment
init(alignment: Alignment) {
self.alignment = alignment
}
public func body(content: Content) -> some View {
content
.getSize($size)
.overlay(alignment: alignment) {
Text("\(cleanFloatString(size.width))⨉\(cleanFloatString(size.height))")
.font(.caption)
.foregroundStyle(.white)
.background(.black.opacity(0.6))
}
}
private func cleanFloatString(_ from: CGFloat) -> String {
from.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", from) : String(format: "%.2f", from)
}
}
public extension View {
func printSize(_ alignment: Alignment = .topLeading) -> some View {
modifier(PrintSizeModifier(alignment: alignment))
}
}
struct PrintSizeModifierPreview: View {
@State private var size = CGSize.zero // 👈
@State private var adjustableHeight = 42.333333
@State private var adjustableWidth = 203.0
var body: some View {
VStack(spacing: 16) {
Text("Text Component")
.padding(100)
.background(.green)
.printSize(.center) // 👈
.printSize(.leading) // 👈
.printSize(.trailing) // 👈
.printSize(.top) // 👈
.printSize(.bottom) // 👈
.printSize(.topLeading) // 👈
.printSize(.topTrailing) // 👈
.printSize(.bottomLeading) // 👈
.printSize(.bottomTrailing) // 👈
Text("Text Component")
.frame(width: adjustableWidth, height: adjustableHeight)
.background(.yellow)
.printSize() // 👈 default .topLeading
Slider(value: $adjustableHeight, in: 0 ... 100)
Slider(value: $adjustableWidth, in: 0 ... 300)
}
.padding(16)
}
}
#Preview {
PrintSizeModifierPreview()
}
Upvotes: 0
Reputation: 257493
Here is possible approach with dynamically sized TextField
.
Note: helper rectReader
is taken from this my post.
Tested with Xcode 11.4 / iOS 13.4 (black border is only for demo)
struct DemoCenterTextField: View {
@State private var value = ""
@State private var frame = CGRect.zero
var body: some View {
return HStack(alignment: .bottom) {
ZStack {
Text(self.value).foregroundColor(Color.clear)
.fixedSize()
.background(rectReader($frame))
TextField("#.#", text: $value)
.multilineTextAlignment(.trailing)
.frame(minWidth: 80, idealWidth: frame.width)
.fixedSize(horizontal: true, vertical: false)
.border(Color.black) // << for demo only
}.font(Font.system(size: 40).bold())
Text("PLN")
}
}
}
Upvotes: 9
Reputation: 3396
You can make the framesize depending on your amount of characters in the TextField to achive what you want:
struct ContentView: View {
@State private var textField = ""
var body: some View {
HStack {
TextField("", text: $textField)
.frame(width: CGFloat(textField.count+10)*5, height: nil)
.textFieldStyle(RoundedBorderTextFieldStyle()) // Just to illustrate the field better.
.multilineTextAlignment(.center)
Text("PLN")
.offset(x: -15, y:0)
}
}
}
Upvotes: 1