News to SwiftUI so please bare with me :) I have a custom textfield setup as a struct in a separate file.
I would like to use onEditingChanged in my content view (the closure would be perfect), is this possible? I have tried with a binding but it isn't a great solution.
import Foundation
import SwiftUI
struct EntryField: View {
var sfSymbolName: String
var placeHolder: String
var prompt: String
#Binding var field: String
var uptodate:Bool = false
var showActivityIndicator:Bool = false
#State var checkMarkOpacity = 0.9
#Binding var editingChanged: Bool
var body: some View {
VStack(alignment: .leading) {
HStack {
Image(systemName: sfSymbolName)
.foregroundColor(.gray)
.font(.custom("SF Pro Light", size: 60))
TextField(placeHolder, text: $field, onEditingChanged: { editing in
editingChanged = editing
}).autocapitalization(.none)
.keyboardType(.decimalPad)
.font(.custom("SF Pro Light", size: 60))
.placeholder(when: field.isEmpty) {
Text("0,00")
.foregroundColor(.white)
.font(.custom("SF Pro Light", size: 60))
}
Image(systemName: "checkmark").opacity(uptodate ? 1 : 0)
.opacity(checkMarkOpacity)
.foregroundColor(.white)
.font(.custom("SF Pro Light", size: 40))
.onAppear(perform: {
withAnimation(.easeIn(duration: 3.0).delay(2.0) ) {
checkMarkOpacity = 0
}
})
.overlay(
ProgressView()
.opacity(showActivityIndicator ? 1 : 0)
//.progressViewStyle(ShadowProgressViewStyle())
.progressViewStyle(RingProgressViewStyle(
configuration: .init(
trackColor: .blue,
fillColor: .white,
lineWidth: 6)))
)
}
.padding(8)
.background(Color(UIColor.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius:10))
Text(prompt)
.fixedSize(horizontal: false, vertical: true)
.font(.caption)
}
}
}
and in my content view I have:
#State private var editingChanged: Bool = false
EntryField(sfSymbolName: "eurosign.square", placeHolder: "", prompt: "Litre Price", field: $closestLitrePrice, uptodate: uptodate, showActivityIndicator: showActivityIndicator, editingChanged: $editingChanged)
thank you
If you don't need it internally then you can pass closure directly from construction to TextField, like
let editingChanged: (Bool) -> Void // << here !!
var body: some View {
VStack(alignment: .leading) {
HStack {
Image(systemName: sfSymbolName)
.foregroundColor(.gray)
.font(.custom("SF Pro Light", size: 60))
TextField(placeHolder, text: $field,
onEditingChanged: editingChanged) // << here !!
.autocapitalization(.none)
Related
so I am trying to have a view update to display a custom view based on a user selection from another view. This is a simple task app project I started to get a better understanding of SwiftUI and have hit my first major roadblock. The custom view is generated from a Tag object from Core Data, so it would be this information that is passed from View 2 to View 1.
I've marked where the update would take place as well as where the action is performed with TODOs. Hopefully I did a good job at explaining what I am hoping to accomplish, nothing I have tried seems to work. I am sure it's something simple but the solution is evading me.
View 1: View that needs to be updated when user returns
View 2: View where selection is made
The View that needs to be updated and its ViewModel.
struct AddTaskView: View {
//MARK: Variables
#Environment(\.managedObjectContext) var coreDataHandler
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = AddTaskViewModel()
#StateObject var taskListViewModel = TaskListViewModel()
#State private var title: String = ""
#State private var info: String = ""
#State private var dueDate = Date()
var screenWidth = UIScreen.main.bounds.size.width
var screenHeight = UIScreen.main.bounds.size.height
var body: some View {
VStack(spacing: 20) {
Text("Add a New Task")
.font(.title)
.fontWeight(.bold)
//MARK: Task.title Field
TextField("Task", text: $title)
.font(.headline)
.padding(.leading)
.frame(height: 55)
//TODO: Update to a specific color
.background(Color(red: 0.9, green: 0.9, blue: 0.9))
.cornerRadius(10)
//MARK: Task.tag Field
HStack {
Text("Tag")
Spacer()
//TODO: UPDATE TO DISPLAY TAG IF SELECTED OTHERWISE DISPLAY ADDTAGBUTTONVIEW
NavigationLink(
destination: TagListView(),
label: {
AddTagButtonView()
}
)
.accentColor(.black)
}
//MARK: Task.info Field
TextEditor(text: $info)
.frame(width: screenWidth - 40, height: screenHeight/4, alignment: .center)
.autocapitalization(.sentences)
.multilineTextAlignment(.leading)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black, lineWidth: 0.5)
)
//MARK: Task.dateDue Field
DatePicker(
"Due Date",
selection: $dueDate,
in: Date()...
)
.accentColor(.black)
.font(.headline)
Spacer()
Button(action: {
viewModel.addTask(taskTitle: title, taskInfo: info, taskDueDate: dueDate)
//Dismiss View if successful
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Add Task")
.frame(width: 150, height: 60)
.font(.headline)
.foregroundColor(.black)
.background(Color.yellow)
.cornerRadius(30)
})
}
.padding()
.navigationBarTitleDisplayMode(.inline)
}
}
final class AddTaskViewModel : ObservableObject {
var coreDataHandler = CoreDataHandler.shared
#Published var tag : Tag?
func addTask(taskTitle: String, taskInfo: String, taskDueDate: Date) {
let newTask = Task(context: coreDataHandler.container.viewContext)
newTask.title = taskTitle
newTask.info = taskInfo
newTask.dateCreated = Date()
newTask.dateDue = taskDueDate
newTask.completed = false
newTask.archived = false
coreDataHandler.save()
}
}
The View where the selection is made and its ViewModel
struct TagListView: View {
#FetchRequest(entity: Tag.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Tag.title, ascending: true)]) var tagList : FetchedResults<Tag>
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = TagListViewModel()
var body: some View {
VStack {
HStack {
Text("Create a Tag")
.font(.system(size: 20))
.fontWeight(.medium)
Spacer()
NavigationLink(
destination: CreateTagView(),
label: {
Image(systemName: "plus.circle")
.font(.system(size: 25))
})
}
Divider()
.padding(.bottom, 10)
ScrollView(.vertical, showsIndicators: false, content: {
if tagList.count != 0 {
LazyVStack(spacing: 20) {
ForEach(tagList, id: \.self) { tag in
let tagColour = Color(red: tag.colourR, green: tag.colourG, blue: tag.colourB, opacity: tag.colourA)
Button {
//TODO: UPDATE ADDTASKVIEW TO DISPLAY THE SELECTED TAG
//Dismiss view
self.presentationMode.wrappedValue.dismiss()
} label: {
TagView(title: tag.title ?? "Tag", color: tagColour, darkText: false)
}
}
}
} else {
Text("Add your first tag.")
}
})
}
.padding()
}
}
final class TagListViewModel : ObservableObject {
}
I have a My Profile form in my app. In the form I have some buttons. Among them 5 buttons are used to present custom drop down menus to select Sax, Blood, Nationality, and so on. However, when I tap these buttons sometime they response some time don't. For example, if I tap them 10 time they response 2 or 3 times. Some time they work for long tap. I have spent days on this matter, but could not identify any problem in my code. Is there anyone who faced this sort of problems? Could anyone figure out what is the actual problem. By the way, all other buttons in my app working absolutely fine.
This is the code for first 3 buttons in red rectangle in the picture
import SwiftUI
struct FormPartTwoView: View {
#Binding var gender: String
#Binding var blood: String
#Binding var nationality: String
#Binding var showingGenderPicker: Bool
#Binding var showingBloodGroupPicker: Bool
#Binding var showingNationalityPicker: Bool
#ObservedObject var userProfile = UserProfileViewModel()
#ObservedObject var userProfileUpdate = UserProfileUpdateViewModel()
var body: some View {
GeometryReader { geometry in
HStack {
Button(action: {
self.showingGenderPicker.toggle()
self.showingBloodGroupPicker = false
self.showingNationalityPicker = false
}) {
VStack {
TextField("Gender", text: self.$gender)
.padding(.horizontal)
.disabled(true)
}
.font(.system(size: 13))
.frame(height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
Button(action: {
self.showingBloodGroupPicker.toggle()
self.showingGenderPicker = false
self.showingNationalityPicker = false
}) {
VStack {
TextField("Blood", text: self.$blood)
.padding(.horizontal)
.disabled(true)
}
.font(.system(size: 13))
.frame(height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
Button(action: {
self.showingNationalityPicker.toggle()
self.showingGenderPicker = false
self.showingBloodGroupPicker = false
}) {
VStack {
TextField("Nationality", text: self.$nationality)
.padding(.horizontal)
.disabled(true)
}
.font(.system(size: 13))
.frame(height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
}
.frame(width: geometry.size.width, height: 40)
}
}
}
struct FormPartTwoView_Previews: PreviewProvider {
static var previews: some View {
FormPartTwoView(gender: .constant(""), blood: .constant(""), nationality: .constant(""), showingGenderPicker: .constant(false), showingBloodGroupPicker: .constant(false), showingNationalityPicker: .constant(false))
}
}
This is the code for second tow Buttons:
import SwiftUI
struct FormPartFiveView: View {
#Binding var district: String
#Binding var upazila: String
#Binding var postcode: String
#Binding var showingUpazilaPicker: Bool
#Binding var showingDistrictPicker: Bool
#ObservedObject var userProfileUpdate = UserProfileUpdateViewModel()
var body: some View {
GeometryReader { geometry in
HStack {
Button(action: {
self.showingDistrictPicker.toggle()
self.showingUpazilaPicker = false
}) {
VStack {
Text("\(self.district == "" ? "District" : self.district)")
.foregroundColor(self.district == "" ? Color.gray : Color.black)
.padding(.horizontal)
}
.font(.system(size: 13))
.frame(width: geometry.size.width/3.2, height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
Button(action: {
self.showingUpazilaPicker.toggle()
self.showingDistrictPicker = false
}) {
VStack {
Text("\(self.upazila == "" ? "Upazila" : self.upazila)")
.foregroundColor(self.upazila == "" ? Color.gray : Color.black)
.padding(.horizontal)
}
.font(.system(size: 13))
.frame(width: geometry.size.width/3.2, height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.buttonStyle(PlainButtonStyle())
VStack {
TextField("Postcode", text: self.$postcode)
.padding(.horizontal)
}
.font(.system(size: 13))
.frame(width: 100, height: 40)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color("Border2"), lineWidth: 1)
)
}
.frame(width: geometry.size.width, height: 40)
}
}
}
struct FormPartFiveView_Previews: PreviewProvider {
static var previews: some View {
FormPartFiveView(district: .constant(""), upazila: .constant(""), postcode: .constant(""), showingUpazilaPicker: .constant(false), showingDistrictPicker: .constant(false))
}
}
I would like to react on a choice of a user. Something similar to this example:
In a 2nd stage would I like to show additional content below each radiobutton, e.g. moving the buttons 2 and 3 from each other in order to give a list of websites for allowing.
So far I haven't found how to do this in SwiftUI.
Many thanks in advance!
Picker(selection: $order.avocadoStyle, label: Text("Avocado:")) {
Text("Sliced").tag(AvocadoStyle.sliced)
Text("Mashed").tag(AvocadoStyle.mashed)
}.pickerStyle(RadioGroupPickerStyle())
This is the code from the 2019 swiftUI essentials keynote (SwiftUI Essentials - WWDC 2019. Around 43 minutes in the video they show this example.
It will look like this:
check this out...an easy to use SwiftUI RadiobuttonGroup for iOS
you can use it like this:
RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in
print("Selected is: \(selected)")
}
and here is the code:
struct ColorInvert: ViewModifier {
#Environment(\.colorScheme) var colorScheme
func body(content: Content) -> some View {
Group {
if colorScheme == .dark {
content.colorInvert()
} else {
content
}
}
}
}
struct RadioButton: View {
#Environment(\.colorScheme) var colorScheme
let id: String
let callback: (String)->()
let selectedID : String
let size: CGFloat
let color: Color
let textSize: CGFloat
init(
_ id: String,
callback: #escaping (String)->(),
selectedID: String,
size: CGFloat = 20,
color: Color = Color.primary,
textSize: CGFloat = 14
) {
self.id = id
self.size = size
self.color = color
self.textSize = textSize
self.selectedID = selectedID
self.callback = callback
}
var body: some View {
Button(action:{
self.callback(self.id)
}) {
HStack(alignment: .center, spacing: 10) {
Image(systemName: self.selectedID == self.id ? "largecircle.fill.circle" : "circle")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: self.size, height: self.size)
.modifier(ColorInvert())
Text(id)
.font(Font.system(size: textSize))
Spacer()
}.foregroundColor(self.color)
}
.foregroundColor(self.color)
}
}
struct RadioButtonGroup: View {
let items : [String]
#State var selectedId: String = ""
let callback: (String) -> ()
var body: some View {
VStack {
ForEach(0..<items.count) { index in
RadioButton(self.items[index], callback: self.radioGroupCallback, selectedID: self.selectedId)
}
}
}
func radioGroupCallback(id: String) {
selectedId = id
callback(id)
}
}
struct ContentView: View {
var body: some View {
HStack {
Text("Example")
.font(Font.headline)
.padding()
RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in
print("Selected is: \(selected)")
}
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentViewDark_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.colorScheme, .dark)
.darkModeFix()
}
}
I just edited #LizJ answer , by adding Binding instead of didTapActive & didTapInactive , so like that it will looks like other SwiftUI elements
import SwiftUI
struct RadioButton: View {
#Binding var checked: Bool //the variable that determines if its checked
var body: some View {
Group{
if checked {
ZStack{
Circle()
.fill(Color.blue)
.frame(width: 20, height: 20)
Circle()
.fill(Color.white)
.frame(width: 8, height: 8)
}.onTapGesture {self.checked = false}
} else {
Circle()
.fill(Color.white)
.frame(width: 20, height: 20)
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
.onTapGesture {self.checked = true}
}
}
}
}
I'm using swift4, Catalina OS and Xcode 11.2 and was having the issue where RadioGroupPickerStyle was unavailable for iOS and .radiogroup just didn't work (it froze in build) so I made my own that's reusable for other occasions. (notice its only the button so you have to handle the logic yourself.) Hope it helps!
import SwiftUI
struct RadioButton: View {
let ifVariable: Bool //the variable that determines if its checked
let onTapToActive: ()-> Void//action when taped to activate
let onTapToInactive: ()-> Void //action when taped to inactivate
var body: some View {
Group{
if ifVariable {
ZStack{
Circle()
.fill(Color.blue)
.frame(width: 20, height: 20)
Circle()
.fill(Color.white)
.frame(width: 8, height: 8)
}.onTapGesture {self.onTapToInactive()}
} else {
Circle()
.fill(Color.white)
.frame(width: 20, height: 20)
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
.onTapGesture {self.onTapToActive()}
}
}
}
}
TO USE: Put this in any file and you can use it as you would any other view anywhere else in the project. (we keep a global folder that has a buttons file in it)
I will use the previous answer of #LizJ and i will add a text after the radio button to resemble (RadioListTile in Flutter)
struct RadioButton: View {
let ifVariable: Bool //the variable that determines if its checked
let radioTitle: String
var onTapToActive: ()-> Void//action when taped to activate
let onTapToInactive: ()-> Void //action when taped to inactivate
var body: some View {
Group{
if ifVariable {
HStack(alignment: .center, spacing: 16) {
ZStack{
Circle()
.fill(AppColors.primaryColor)
.frame(width: 20, height: 20)
Circle()
.fill(Color.white)
.frame(width: 8, height: 8)
}.onTapGesture {self.onTapToInactive()}
Text(radioTitle)
.font(.headline)
}
} else {
HStack(alignment: .center, spacing: 16){
Circle()
.fill(Color.white)
.frame(width: 20, height: 20)
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
.onTapGesture {self.onTapToActive()}
Text(radioTitle)
.font(.headline)
}
}
}
}
I will also provide an example for the selection logic
we will create a enum for radio cases
enum PaymentMethod: Int {
case undefined = 0
case credit = 1
case cash = 2
}
then we will create #State variable to carry the selection, i will not recreate another SwiftUI view but only explain the basic concept without any boilerplate code
struct YourView: View {
#State private var paymentMethod: PaymentMethod
var body: some View {
RadioButton(ifVariable: paymentMethod == PaymentMethod.credit,radioTitle: "Pay in Credit", onTapToActive: {
paymentMethod = .credit
}, onTapToInactive: {})
RadioButton(ifVariable: paymentMethod == PaymentMethod.cash,radioTitle: "Pay in Cash", onTapToActive: {
paymentMethod = .cash
}, onTapToInactive: {})
}
}
with this previous code you can toggle between radio buttons in SwiftUI with a text after each selection to resemble (RadioListTile in Flutter)
I have a problem with a view. The view in question once entered in it, render the screen for a moment and then disappears. I Load data from firebase. At the hierarchical level it is the third view
VIEW A -> VIEW B -> VIEW C
if arrive in C from B, i've the problem, if arrive from A the problem its not present.
The problem is "self.lineup.fetchHomeTeam" after onAppear return empty
The data passed from ViewB To ViewC are correct
VIEW C (TeamsModuleView) -> Page with problem
struct TeamsModuleView: View {
#ObservedObject var lineup = LineupViewModel()
#EnvironmentObject var settings: UserSettings
var body: some View {
ScrollView(.vertical) {
Group {
VStack(spacing: 20, content: {
ForEach(lineup.lineupHome, id: \.self) { module in
HStack(alignment: .top, spacing: 10, content: {
ForEach(module.name, id: \.self) { name in
Group {
Spacer()
VStack(alignment: .center, spacing: 0, content: {
Spacer().frame(height: 20)
Image("home")
.resizable()
.frame(width: 30, height: 30)
Text(name)
.foregroundColor(Color.white)
.font(.system(size: 10))
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
Spacer().frame(height: 5)
})
Spacer()
}
}
})
}
ForEach(lineup.lineupAway, id: \.self) { module in
HStack(alignment: .top, spacing: 10, content: {
ForEach(module.name, id: \.self) { name in
Group {
Spacer()
VStack(alignment: .center, spacing: 0, content: {
Spacer().frame(height: 5)
Image("transfert")
.resizable()
.frame(width: 30, height: 30)
Text(name)
.foregroundColor(Color.white)
.font(.system(size: 10))
.frame(maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
Spacer().frame(height: 20)
})
Spacer()
}
}
})
}
})
.background(
Image("field3")
.resizable()
.aspectRatio(contentMode: .fill)
).edgesIgnoringSafeArea(.all)
}
}.onAppear {
self.lineup.fetchHomeTeam(fixturesId: String(self.settings.fixtureId), teamId: String(self.settings.teamHomeId), team: self.settings.teamHome)
self.lineup.fetchAwayTeam(fixturesId: String(self.settings.fixtureId), teamId: String(self.settings.teamAwayId), team: self.settings.teamAway)
}.onDisappear {
print(self.lineup.lineupHome.isEmpty)
}
.navigationBarTitle("Formazione", displayMode: .inline) //Return true i dont why
}
}
struct TeamsModuleView_Previews: PreviewProvider {
static var previews: some View {
TeamsModuleView()
}
}
LineupViewModel
final class LineupViewModel: ObservableObject {
#Published var lineup = Lineup()
#Published var lineupHome = [LineupView]()
#Published var lineupAway = [LineupView]()
func fetchHomeTeam(fixturesId: String, teamId: String, team: String) {
Webservices().getLineUp(fixturesId: fixturesId, teamId: teamId, team: team) {
self.lineup = $0
var lineupModTemp = [LineupView]()
-
-
-
DispatchQueue.main.async {
self.lineupHome = lineupModTemp
}
}
}
func fetchAwayTeam(fixturesId: String, teamId: String, team: String) {
Webservices().getLineUp(fixturesId: fixturesId, teamId: teamId, team: team) {
self.lineup = $0
var lineupModTemp = [LineupView]()
-
-
-
DispatchQueue.main.async {
self.lineupAway = lineupModTemp
}
}
}
}
UserSettings(the real data are modify in View B onclik)
class UserSettings: ObservableObject {
#Published var teamHomeId = 505
#Published var teamAwayId = 518
#Published var teamHome = "Brescia"
#Published var teamAway = "Inter"
#Published var fixtureId = 232614
}
I am trying to create a list of options for a user to choose from. The debug preview shows the general look and feel. My problem is that passing nil to .lineLimit in MultipleChoiceOption doesn't allow the text to grow beyond 1 line. How can I correct this?
struct Card<Content: View> : View {
private let content: () -> Content
init(content: #escaping () -> Content) {
self.content = content
}
private let shadowColor = Color(red: 69 / 255, green: 81 / 255, blue: 84 / 255, opacity: 0.1)
var body: some View {
ZStack {
self.content()
.padding()
.background(RoundedRectangle(cornerRadius: 22, style: .continuous)
.foregroundColor(.white)
.shadow(color: shadowColor, radius: 10, x: 0, y: 5)
)
}
.aspectRatio(0.544, contentMode: .fit)
.padding()
}
}
struct MultipleChoiceOption : View {
var option: String
#State var isSelected: Bool
var body: some View {
ZStack {
Rectangle()
.foregroundColor(self.isSelected ? .gray : .white)
.cornerRadius(6)
.border(Color.gray, width: 1, cornerRadius: 6)
Text(self.option)
.font(.body)
.foregroundColor(self.isSelected ? .white : .black)
.padding()
.multilineTextAlignment(.leading)
.lineLimit(nil)
}
}
}
struct MultipleChoice : View {
#State var selectedIndex = 1
var options: [String] = [
"Hello World",
"How are you?",
"This is a longer test This is a longer test This is a longer test This is a longer test This is a longer test This is a longer test"
]
var body: some View {
GeometryReader { geometry in
ScrollView {
VStack(alignment: .leading, spacing: 12) {
ForEach(self.options.indices) { i in
MultipleChoiceOption(option: self.options[i],
isSelected: i == self.selectedIndex)
.tapAction { self.selectedIndex = i }
}
}
.frame(width: geometry.size.width)
}
}
.padding()
}
}
struct MultipleChoiceCard : View {
var question: String = "Is this a question?"
var body: some View {
Card {
VStack(spacing: 30) {
Text(self.question)
MultipleChoice()
}
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
// NavigationView {
VStack {
MultipleChoiceCard()
Button(action: {
}) {
Text("Next")
.padding()
.foregroundColor(.white)
.background(Color.orange)
.cornerRadius(10)
}
}
.padding()
// .navigationBarTitle(Text("Hello"))
// }
}
}
#endif
Please see this answer for Xcode 11 GM:
https://stackoverflow.com/a/56604599/30602
Summary: add .fixedSize(horizontal: false, vertical: true) — this worked for me in my use case.
The modifier fixedSize() prevents the truncation of multiline text.
Inside HStack
Text("text").fixedSize(horizontal: false, vertical: true)
Inside VStack
Text("text").fixedSize(horizontal: true, vertical: false)
There is currently a bug in SwiftUI causing the nil lineLimit to not work.
If you MUST fix this now, you can wrap a UITextField:
https://icalvin.dev/post/403
I had the same problem and used this workaround:
Add the modifier:
.frame(idealHeight: .infinity)