How to remove/adjust separators in List? - list

Is there a way to remove separators or adjust separator insets in List view in SwiftUI?
In UIKit it can be achieved through
tableView.separatorStyle = .none
and
tableview.separatorInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
What are the corresponding SwiftUI alternatives?

For ios 13 not for ios 14
You can remove separaters using: UITableView.appearance().separatorStyle = .none in SwiftUI
Just add on
List() {
}.onAppear {
UITableView.appearance().separatorColor = .clear
}
or
struct SomeListView : View {
init( ) {
UITableView.appearance().separatorStyle = .none
}
var body : some View {
Text("TEST")
}
struct CallList : View {
var body : some View {
List() {
SomeListView()
}
}
}

iOS 15.0+
Mac Catalyst 15.0+
listRowSeparator(_:edges:)
Sets the display mode for the separator associated with this specific row.
https://developer.apple.com/
List {
ForEach(0..<10, id: \.self) { number in
Text("Text\(number)")
}.listRowSeparator(.hidden)
}
iOS 14.0+
struct ListRowSeperatorModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
content.listRowSeparator(.hidden)
} else {
content.onAppear {
UITableView.appearance().separatorStyle = .none
}
.onDisappear {
UITableView.appearance().separatorStyle = .singleLine
}
}
}
}
extension View {
func hideListRowSeparator() -> some View {
return self.modifier(ListRowSeperatorModifier())
}
}
Use .hideListRowSeparator() on ForEach.
List {
ForEach(0..<10, id: \.self) { number in
Text("Text\(number)")
}.hideListRowSeparator()
}

Searched so much to adjust insets for line separators in list. You do not need to do OnAppear(), just adjust the 'padding()' modifier for list.Simple!
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 20))
To further tune the row inside list, you can use this -
Use this modifier:
.listRowInsets(EdgeInsets(....))
List {
Text("test")
.listRowInsets(EdgeInsets(top: -20, leading: -20, bottom: -20, trailing: -20))
}

This can all be done in SwiftUI
To remove separators in compare with the good old way in UIKit tableView.separatorStyle = .none, add this line in init or the table view's onAppear method:
init() {
UITableView.appearance().separatorStyle = .none
}
To adjust separator inset in compare with the line in UIKit tableview.separatorInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18), add this line in init or onAppear method:
List(...){
...
}.onAppear() {
UITableView.appearance().separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 50)
}

remove separator -> set clear color
init() {
UITableView.appearance().separatorColor = .clear
}

In SwiftUI:
Remove Separators
init() {
UITableView.appearance().separatorStyle = .none //remove separators
}
var body: some View {
List {
Text("Index 1")
Text("Index 2")
Text("Index 3")
Text("Index 4")
}
}

For the latter you can use listRowInsets:
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.listRowInsets(EdgeInsets(top: 0, left: 18, bottom: 0, right: 18))

Related

How can the navigation bar title be set as the item's name upon tapping on a specific item?

I'm creating a dinner menu with various food item's that can be tapped. Each item is wrapped in a NavigationLink that leads to it's detail page.
How can the item's name be placed as the navigation bar title for each item? Here's the entire MenuItemsView struct and a gif to demonstrate where I'd like the food item title just as in the previous Menu screen.
struct MenuItemsView: View {
let food = (1...12).map { "Food \($0)" }
let drinks = (1...8).map { "Drink \($0)" }
let dessert = (1...4).map { "Dessert \($0)" }
let columns = [
GridItem(.adaptive(minimum: 80))
]
var body: some View {
NavigationView {
ScrollView {
VStack {
Text("Food")
.frame(maxWidth: .infinity, alignment: .leadingFirstTextBaseline)
.font(.title)
.padding(.init(top: -5, leading: 16, bottom: 0, trailing: 0))
LazyVGrid(columns: columns, spacing: 5.0) {
ForEach(food, id: \.self) { item in
NavigationLink(destination: MenuItemDetailsView()) {
VStack {
ColorSquare(color: .black)
Text(item)
}
.padding(.init(top: 0, leading: 10, bottom: 0, trailing: 10))
}
}
}
}
VStack {
Text("Drinks")
.frame(maxWidth: .infinity, alignment: .leadingFirstTextBaseline)
.font(.title)
.padding(.init(top: -5, leading: 16, bottom: 0, trailing: 0))
LazyVGrid(columns: columns, spacing: 5.0) {
ForEach(drinks, id: \.self) { item in
VStack {
ColorSquare(color: .black)
Text(item)
}
.padding(.init(top: 0, leading: 10, bottom: 0, trailing: 10))
}
}
}
VStack {
Text("Desert")
.frame(maxWidth: .infinity, alignment: .leadingFirstTextBaseline)
.font(.title)
.padding(.init(top: -5, leading: 16, bottom: 0, trailing: 0))
LazyVGrid(columns: columns, spacing: 5.0) {
ForEach(dessert, id: \.self) { item in
VStack {
ColorSquare(color: .black)
Text(item)
}
.padding(.init(top: 0, leading: 10, bottom: 0, trailing: 10))
}
}
}
}
.navigationBarTitle("Menu")
.navigationBarItems(trailing:
Button(action: {
print("Edit button pressed...")
}) {
NavigationLink(destination: MenuItemsOptionView()) {
Image(systemName: "slider.horizontal.3")
}
}
)
}
}
}
Food Details Demonstration
As a bonus, if anyone can tell me how to properly line up the Color Squares with their respective category name and Menu title, I'd appreciate it a lot lol. Thanks!
#State itemname:String
var body: some View {
...
.navigationTitle(){
Text(itemname)
}
I was able to figure out this issue. Here's how:
I passed in itemName: item as a NavigationLink parameter as the destination. In the MenuItemDetailsView file, the body was set up with a Text view and navigationBarTitle modifier with itemName passed in. itemName was created above the MenuItemDetailsView struct before being passed of course. Here is the MenuItemDetailsView and MenuItemsView code that solved the problem as well as a quick demonstration:
MenuItemDetailsView
import SwiftUI
struct MenuItemDetailsView: View {
var itemName: String
var body: some View {
Text(itemName)
.navigationBarTitle(itemName)
}
}
struct MenuItemDetailsView_Previews: PreviewProvider {
static var previews: some View {
let food = (1...12).map { "Food \($0)" }
return Group {
ForEach(food, id: \.self) { item in
NavigationView {
MenuItemDetailsView(itemName: item)
}
.previewDisplayName(item)
}
}
}
}
MenuItemsView
import SwiftUI
struct ColorSquare: View {
let color: Color
var body: some View {
color
.frame(width: 100, height: 100)
}
}
struct MenuItemsView: View {
let food = (1...12).map { "Food \($0)" }
let drinks = (1...8).map { "Drink \($0)" }
let dessert = (1...4).map { "Dessert \($0)" }
let columns = [
GridItem(.adaptive(minimum: 80))
]
var body: some View {
NavigationView {
ScrollView {
VStack {
Text("Food")
.frame(maxWidth: .infinity, alignment: .leadingFirstTextBaseline)
.font(.title)
.padding(.init(top: -5, leading: 16, bottom: 0, trailing: 0))
LazyVGrid(columns: columns, spacing: 5.0) {
ForEach(food, id: \.self) { item in
NavigationLink(destination: MenuItemDetailsView(itemName: item)) {
VStack {
ColorSquare(color: .black)
Text(item)
}
.padding(.init(top: 0, leading: 10, bottom: 0, trailing: 10))
}
}
}
}...more code
Food Item Nav Title Solved Demo

Hiding Picker's focus border on watchOS in SwiftUI

I need to use a Picker view but I don't see any options to hide the green focus border.
Code:
#State private var selectedIndex = 0
var values: [String] = (0 ... 12).map { String($0) }
var body: some View {
Picker(selection: $selectedIndex, label: Text("")) {
ForEach(0 ..< values.count) {
Text(values[$0])
}
}
.labelsHidden()
}
The following extension puts a black overlay over the picker border.
Result
Code
extension Picker {
func focusBorderHidden() -> some View {
let isWatchOS7: Bool = {
if #available(watchOS 7, *) {
return true
}
return false
}()
let padding: EdgeInsets = {
if isWatchOS7 {
return .init(top: 17, leading: 0, bottom: 0, trailing: 0)
}
return .init(top: 8.5, leading: 0.5, bottom: 8.5, trailing: 0.5)
}()
return self
.overlay(
RoundedRectangle(cornerRadius: isWatchOS7 ? 8 : 7)
.stroke(Color.black, lineWidth: isWatchOS7 ? 4 : 3.5)
.offset(y: isWatchOS7 ? 0 : 8)
.padding(padding)
)
}
}
Usage
Make sure .focusBorderHidden() is the first modifier.
Picker( [...] ) {
[...]
}
.focusBorderHidden()
[...]
On the Picker, something like this can be added to cover up the green border.
#ScaledMetric var borderWidth: CGFloat = 5 // or it can be 3
Picker {
...
}.border(Color.black, width: borderWidth)
Adding a mask of cornerRadius of whatever required but with a padding of 2 or over (so as to mask the outer edge of the view) as the green border width on the watch tends to be around 2... will do the trick
.mask(RoundedRectangle(cornerRadius: 12).padding(2))
I like the border method from Ray Hunter too but to keep things tidy and simple I rather stay away from having lots and lots of #... variables

Dynamically size a GeometryReader height based on its elements

I'm trying to do something that's pretty straight forward in my mind.
I want a subview of a VStack to dynamically change its height based on its content (ProblematicView in the sample below).
It usually works pretty well, but in this case ProblematicView contains a GeometryReader (to simulate a HStack over several lines).
However, the GeometryReader greedily takes all the space it can (the expected behavior happens if you remove the GeometryReader and it's content). Unfortunately on the Parent view (UmbrellaView in the sample below), the UmbrellaView VStack assigns 50% of itself to the ProblematicView instead of the minimal size to display the content of the view.
I've spend a few hours playing with min/ideal/maxHeight frame arguments, to no avail.
Is what I'm trying to achieve doable?
I added pictures at the bottom to clarify visually.
struct UmbrellaView: View {
var body: some View {
VStack(spacing: 0) {
ProblematicView()
.background(Color.blue)
ScrollView(.vertical) {
Group {
Text("A little bit about this").font(.system(size: 20))
Divider()
}
Group {
Text("some").font(.system(size: 20))
Divider()
}
Group {
Text("group").font(.system(size: 20)).padding(.bottom)
Divider()
}
Group {
Text("content").font(.system(size: 20))
}
}
}
}
}
struct ProblematicView: View {
var body: some View {
let tags: [String] = ["content", "content 2 ", "content 3"]
var width = CGFloat.zero
var height = CGFloat.zero
return VStack(alignment: .center) {
Text("Some reasonnably long text that changes dynamically do can be any size").background(Color.red)
GeometryReader { g in
ZStack(alignment: .topLeading) {
ForEach(tags, id: \.self) { tag in
TagView(content: tag, color: .red, action: {})
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width)
{
width = 0
height -= d.height
}
let result = width
if tag == tags.last! {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if tag == tags.last! {
height = 0 // last item
}
return result
})
}
}.background(Color.green)
}.background(Color.blue)
}.background(Color.gray)
}
}
struct TagView: View {
let content: String
let color: Color
let action: () -> Void?
var body: some View {
HStack {
Text(content).padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
Button(action: {}) {
Image(systemName: "xmark.circle").foregroundColor(Color.gray)
}.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 7))
}
.background(color)
.cornerRadius(8.0)
}
}
struct ProblematicView_Previews: PreviewProvider {
static var previews: some View {
return ProblematicView()
}
}
struct UmbrellaView_Previews: PreviewProvider {
static var previews: some View {
return UmbrellaView()
}
}
Due to "hen-egg" problem in nature of GeometryReader the solution for topic question is possible only in run-time, because 1) initial height is unknown 2) it needs to calculate internal size based on all available external size 3) it needs to tight external size to calculated internal size.
So here is possible approach (with some additional fixes in your code)
Preview 2-3) Run-time
Code:
struct ProblematicView: View {
#State private var totalHeight = CGFloat(100) // no matter - just for static Preview !!
#State private var tags: [String] = ["content", "content 2 ", "content 3", "content 4", "content 5"]
var body: some View {
var width = CGFloat.zero
var height = CGFloat.zero
return VStack {
Text("Some reasonnably long text that changes dynamically do can be any size").background(Color.red)
VStack { // << external container
GeometryReader { g in
ZStack(alignment: .topLeading) { // internal container
ForEach(self.tags, id: \.self) { tag in
TagView(content: tag, color: .red, action: {
// self.tags.removeLast() // << just for testing
})
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width)
{
width = 0
height -= d.height
}
let result = width
if tag == self.tags.last! {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if tag == self.tags.last! {
height = 0 // last item
}
return result
})
}
}.background(Color.green)
.background(GeometryReader {gp -> Color in
DispatchQueue.main.async {
// update on next cycle with calculated height of ZStack !!!
self.totalHeight = gp.size.height
}
return Color.clear
})
}.background(Color.blue)
}.frame(height: totalHeight)
}.background(Color.gray)
}
}
struct TagView: View {
let content: String
let color: Color
let action: (() -> Void)?
var body: some View {
HStack {
Text(content).padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
Button(action: action ?? {}) {
Image(systemName: "xmark.circle").foregroundColor(Color.gray)
}.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 7))
}
.background(color)
.cornerRadius(8.0)
}
}
Based on #Asperi's code I've implemented a universal solution. It works in Previews and is compatible with iOS 13+.
My solution does not use DispatchQueue.main.async and has a convenient #ViewBuilder for you to toss in any View you like. Put the VerticalFlow in VStack or ScrollView. Set hSpacing and vSpacing to items. Add padding to the whole View.
Simple example:
struct ContentView: View {
#State var items: [String] = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight"]
var body: some View {
VerticalFlow(items: $items) { item in
Text(item)
}
}
}
VerticalFlow.swift:
import SwiftUI
struct VerticalFlow<Item, ItemView: View>: View {
#Binding var items: [Item]
var hSpacing: CGFloat = 20
var vSpacing: CGFloat = 10
#ViewBuilder var itemViewBuilder: (Item) -> ItemView
#SwiftUI.State private var size: CGSize = .zero
var body: some View {
var width: CGFloat = .zero
var height: CGFloat = .zero
VStack {
GeometryReader { geometryProxy in
ZStack(alignment: .topLeading) {
ForEach(items.indices, id: \.self) { i in
itemViewBuilder(items[i])
.alignmentGuide(.leading) { dimensions in
if abs(width - dimensions.width) > geometryProxy.size.width {
width = 0
height -= dimensions.height + vSpacing
}
let leadingOffset = width
if i == items.count - 1 {
width = 0
} else {
width -= dimensions.width + hSpacing
}
return leadingOffset
}
.alignmentGuide(.top) { dimensions in
let topOffset = height
if i == items.count - 1 {
height = 0
}
return topOffset
}
}
}
.readVerticalFlowSize(to: $size)
}
}
.frame(height: size.height > 0 ? size.height : nil)
}
}
struct VerticalFlow_Previews: PreviewProvider {
#SwiftUI.State static var items: [String] = [
"One 1", "Two 2", "Three 3", "Four 4", "Eleven 5", "Six 6",
"Seven 7", "Eight 8", "Nine 9", "Ten 10", "Eleven 11",
"ASDFGHJKLqwertyyuio d fadsf",
"Poiuytrewq lkjhgfdsa mnbvcxzI 0987654321"
]
static var previews: some View {
VStack {
Text("Text at the top")
VerticalFlow(items: $items) { item in
VerticalFlowItem(systemImage: "pencil.circle", title: item, isSelected: true)
}
Text("Text at the bottom")
}
ScrollView {
VStack {
Text("Text at the top")
VerticalFlow(items: $items) { item in
VerticalFlowItem(systemImage: "pencil.circle", title: item, isSelected: true)
}
Text("Text at the bottom")
}
}
}
}
private struct VerticalFlowItem: View {
let systemImage: String
let title: String
#SwiftUI.State var isSelected: Bool
var body: some View {
HStack {
Image(systemName: systemImage).font(.title3)
Text(title).font(.title3).lineLimit(1)
}
.padding(10)
.foregroundColor(isSelected ? .white : .blue)
.background(isSelected ? Color.blue : Color.white)
.cornerRadius(40)
.overlay(RoundedRectangle(cornerRadius: 40).stroke(Color.blue, lineWidth: 1.5))
.onTapGesture {
isSelected.toggle()
}
}
}
private extension View {
func readVerticalFlowSize(to size: Binding<CGSize>) -> some View {
background(GeometryReader { proxy in
Color.clear.preference(
key: VerticalFlowSizePreferenceKey.self,
value: proxy.size
)
})
.onPreferenceChange(VerticalFlowSizePreferenceKey.self) {
size.wrappedValue = $0
}
}
}
private struct VerticalFlowSizePreferenceKey: PreferenceKey {
static let defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
let next = nextValue()
if next != .zero {
value = next
}
}
}

Remove/change section header background color in SwiftUI List

With a simple List in SwiftUI, how do I change/remove the standard background color for the section header
struct ContentView : View {
var body: some View {
List {
ForEach(0...3) { section in
Section(header: Text("Section")) {
ForEach(0...3) { row in
Text("Row")
}
}
}
}
}
}
No need to change appearance of all lists or do anything strange, just:
(Optional) Put .listStyle(GroupedListStyle()) on your List if you do not want sticky headers.
Set the listRowInsets on the section to 0.
Set the Section.backgroundColor to clear to remove the default color, or whatever color you want to color it.
Example:
List {
Section(header: HStack {
Text("Header")
.font(.headline)
.foregroundColor(.white)
.padding()
Spacer()
}
.background(Color.blue)
.listRowInsets(EdgeInsets(
top: 0,
leading: 0,
bottom: 0,
trailing: 0))
) {
// your list items
}
}.listStyle(GroupedListStyle()) // Leave off for sticky headers
The suggested solutions works until you decide to clear your List header background color.
Better solutions for List header custom color:
1.This solution effects all of the List sections in your app: (or move it to your AppDelegate class)
struct ContentView: View {
init() {
UITableViewHeaderFooterView.appearance().tintColor = UIColor.clear
}
var body: some View {
List {
ForEach(0 ..< 3) { section in
Section(header:
Text("Section")
) {
ForEach(0 ..< 3) { row in
Text("Row")
}
}
}
}
}
}
2.With this solution you can have custom List header background color for each list in your app:
struct ContentView: View {
init() {
UITableViewHeaderFooterView.appearance().tintColor = UIColor.clear
}
var body: some View {
List {
ForEach(0 ..< 3) { section in
Section(header:
HStack {
Text("Section")
Spacer()
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.background(Color.blue)
) {
ForEach(0 ..< 3) { row in
Text("Row")
}
}
}
}
}
}
In beta 4, relativeWidth was deprecated. Code updated to reflect that.
Unfortunately, there's no quick parameter to set the background color. However, you can still do it:
import SwiftUI
struct ContentView : View {
var body: some View {
List {
ForEach(0...3) { section in
Section(header:
CustomHeader(
name: "Section Name",
color: Color.yellow
)
) {
ForEach(0...3) { row in
Text("Row")
}
}
}
}
}
}
struct CustomHeader: View {
let name: String
let color: Color
var body: some View {
VStack {
Spacer()
HStack {
Text(name)
Spacer()
}
Spacer()
}
.padding(0).background(FillAll(color: color))
}
}
struct FillAll: View {
let color: Color
var body: some View {
GeometryReader { proxy in
self.color.frame(width: proxy.size.width * 1.3).fixedSize()
}
}
}
I tried to use the custom header code above, and unfortunately could not get it to work in beta 6. That led me to the use of a ViewModifier:
public struct sectionHeader: ViewModifier{
var backgroundColor:Color
var foregroundColor:Color
public func body(content: Content) -> some View {
content
.padding(20)
.frame(width: UIScreen.main.bounds.width, height: 28,alignment:
.leading)
.background(backgroundColor)
.foregroundColor(foregroundColor)
}}
Which can be added to the sections in your list as follows:
struct ContentView: View {
#ObservedObject var service = someService()
var body: some View {
NavigationView {
List{
ForEach(someService.sections) {section in
Section(header: Text(section.title).modifier(sectionHeader(backgroundColor: Color(.systemBlue), foregroundColor: Color(.white)))){
Hope that helps somebody!
I was able to get the header to be clear (become white in my case) by using the custom modifiers. I needed to use listStyle() modifiers and all of the above didn't work for me.
Hope it helps someone else!
List {
Section(header: HStack {
Text("Header Text")
.font(.headline)
.padding()
Spacer()
}
) {
ForEach(0...3) { row in
Text("Row")
}
}
}.listStyle(GroupedListStyle()).listSeparatorStyle()
public struct ListSeparatorStyleModifier: ViewModifier {
public func body(content: Content) -> some View {
content.onAppear {
UITableView.appearance().separatorStyle = .singleLine
UITableView.appearance().separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
UITableViewHeaderFooterView.appearance().tintColor = .clear
UITableView.appearance().backgroundColor = .clear // tableview background
UITableViewCell.appearance().backgroundColor = .clear
}
}
}
extension View {
public func listSeparatorStyle() -> some View {
modifier(ListSeparatorStyleModifier())
}
}
You have to use a rectangle combined with .listRowInsets on the view for your header section
Section(header: headerSectionView) {
Text("MySection")
}
private var headerSectionView: some View {
Rectangle()
.fill(Color.blue) // will make a blue header background
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.overlay(
Text("My Section Title")
.padding(.horizontal), // You need this to add back the padding
alignment: .leading
)
}
I found a better solution
UITableViewHeaderFooterView.appearance().backgroundView = .init()
Since iOS 16 you could use the scrollContentBackground(_:) modifier.
Reference here
In your example:
struct ContentView : View {
var body: some View {
List {
ForEach(0...3) { section in
Section(header: Text("Section")) {
ForEach(0...3) { row in
Text("Row")
}
}
}
}
.scrollContentBackground(.hidden)
}
}
Another way you can do it by setting the frame of the header:
VStack {
List {
ForEach(0...3) { section in
Section(header:
Text("Section")
.frame(minWidth: 0, maxWidth: .infinity,alignment:.leading)
.background(Color.blue.relativeWidth(2))
) {
ForEach(0...3) { row in
Text("Row")
}
}
}
}
}

SwiftUI multiline text always truncating at 1 line

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)