I want to have this list at the bottom of the screen. I know that there are only 4 Rows (or 5). I tried to use UITableView.appearance().contentInset.top = …
But the value depends on the screenHeight, the rowHeight and the safeArea. When I tried to use this after onAppeared() the largeTitle changed to inLine. I need it because then I need GeometryReader for the inset and the listrow. I also set the TableView to UiTableView.appearance().scrollingEnabled = false
I don’t know how to archive it. I would like to have the title always large and the screen not scrollable.
Thanks for any help.
var body:some View{
NavigationView{
List{
Text(„hi“)
Text(„hi“)
Text(„hi“)
Text(„hi“)
}
.navigationBarTitle(„Hi“, displayMode: .large)
}
}
Related
I need to know the current view width and height, I managed to get it using the following code but it works only if I manually select the NavigationView.
I also tried to include the GeometryReader { ... } part in another view but it modifies the position of other elements.
My question to you, is there a way to call NavigationView automatically (just in order to get the view sizes) and return to the main view without manual action?
Or is there another way to get the current view width and height?
struct NavigationView : View {
#ObservedObject var myMaze = Maze.sharedInstance
var body : some View {
GeometryReader { geometry in
Path { path in
myMaze.viewWidth = geometry.size.width
myMaze.viewHeight = geometry.size.height
}
}
}
}
Give this code a try, it works in my case:
For height:
view.getMeasuredHeight()
For width:
view.getMeasuredWidth()
Note: this code won't work in onCreate because the view hasn't been drawn yet, add a listener or a timer depending on your needs.
I'm currently using a List with a GroupedListStyle(), and with a .regular horizontalSizeClass (e.g an iPad in landscape mode), this style of List automatically creates some padding to each section, like this:
struct ListView: View {
var body: some View {
List {
Section {
Text("One")
Text("Two")
Text("Three")
}
}.listStyle(GroupedListStyle())
}
}
This is great, I want this. But since I need to display some elements inside the rows with a fixed width, I need to know the actual width of the row inside the List.
I've tried wrapping the List with a GeometryReader, but the width doesn't match the row's width because of the padding around the entire Section.
struct ListView: View {
var body: some View {
GeometryReader { geometry in
List {
Section {
Text("One") // <-- geometry.size.width != Text.width
Text("Two")
Text("Three")
}
}.listStyle(GroupedListStyle())
}
}
}
So I've tried using the PreferenceKey method explained here by putting a GeometryReader in the listRowBackground modifier of the row, but the value is never updated.
Is there a working way to get the size of the parent, without using a GeometryReader around the parent (as it is finnicky on a List, and needs to be specified Height and Width to work properly)?
I've had to resort to a UITableView with style .insetGrouped, implemented with a Representable and used the property layoutMargins that gives exactly the size of the horizontal margins in this size class.
I want to use NavigationView together with the ScrollView, but I am not seeing List items.
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView{
VStack {
Text("Some stuff 1")
List{
Text("one").padding()
Text("two").padding()
Text("three").padding()
}
Text("Some stuff 2")
}
}
}
}
}
All I see is the text. If I remove ScrollView I see it all, but the text is being pushed to the very bottom. I simply want to be able to add List and Views in a nice scrollable page.
The ScrollView expects dimension from content, but List expects dimension from container - as you see there is conflict, so size for list is undefined, and a result rendering engine just drop it to avoid disambiguty.
The solution is to define some size to List, depending of your needs, so ScrollView would now how to lay out it, so scroll view could scroll entire content and list could scroll internal content.
Eg.
struct ContentView: View {
#Environment(\.defaultMinListRowHeight) var minRowHeight
var body: some View {
NavigationView {
ScrollView{
VStack {
Text("Some stuff 1")
List {
Text("one").padding()
Text("two").padding()
Text("three").padding()
}.frame(minHeight: minRowHeight * 3).border(Color.red)
Text("Some stuff 2")
}
}
}
}
}
Just wanted to throw out an answer that fixed what I was seeing very similar to the original problem - I had put a Label() item ahead of my List{ ... } section, and when I deleted that Label() { } I was able to see my List content again. Possibly List is buggy with other items surrounding it (Xcode 13 Beta 5).
For example, if my UI needed to display a length Measurement in human readable form, it might want to choose from one of the following formats to display one inch:
1"
1 in
1 inch
one inch
So far I have tried:
truncationMode(_:): only accepts positional argument, no option for custom truncation
GeometryReader: tells me what space is available (super useful!) but I don't see how to dynamically select a dynamically sized sub-view, seems to be optimized for generating fixed sized sub-views or overflowing the position
When I try to find another app that might have solved this problem it seems that they all rearrange the layout on orientation or other size change. I want to continue to have a single HStack of Text views that fit the space, keeping all the important information from being truncated when possible.
Let's define this View:
struct FlexibleTextView: View {
let possibleTexts: [String]
var body: some View {
GeometryReader { geometry in
Text(self.possibleTexts.last(where: { $0.size(withAttributes: [.font: UIFont.systemFont(ofSize: 17.0)]).width < geometry.size.width }) ?? self.possibleTexts[0])
.lineLimit(1)
}
}
init(_ possibleTexts: [String]) {
self.possibleTexts = possibleTexts.sorted {
$0.size(withAttributes: [.font: UIFont.systemFont(ofSize: 17.0)]).width < $1.size(withAttributes: [.font: UIFont.systemFont(ofSize: 17.0)]).width
}
}
}
When you init it, the possible texts are automatically sorted by their actual width. It takes the last one (so the one width the greatest width) where the width is smaller than the width of the container, which we get from GeometryReader. If even the first, so the smallest text is to big, (so .last(where: { ... }) will return nil), we still use that first text, but you could also change this yourself to whatever you would like.
Here's an interactive example:
struct ContentView: View {
#State var width: CGFloat = 80
var body: some View {
VStack {
FlexibleTextView(["1\"", "1 in", "1 inch", "one inch"])
.frame(width: width, height: 17)
.border(Color.red)
Slider(value: $width, in: 10 ... 80)
}
.padding()
}
}
With the slider, you can adjust the width to see the effect.
I have some troubles with dynamically changing List height that dependent on elements count.
I tried this solution but it didn't work.
List {
ForEach(searchService.searchResult, id: \.self) { item in
Text(item)
.font(.custom("Avenir Next Regular", size: 12))
}
}.frame(height: CGFloat(searchService.searchResult.count * 20))
TL;DR
This is not how the designers of SwiftUI want you to use lists. Either you will have to come up with a hacky solution that will probably break in the future (see below), or use something other than a list.
Background
SwiftUI tends to have two types of Views
Those designed to be easily modifiable and composable, providing unlimited customizability for a unique look and feel.
Those designed to provide a standard, consistent feel to some type of interaction, regardless of what app they are used in.
An example of type 1 would be Text. You can change font size, weight, typeface, color, background, padding, etc. It is designed for you to modify it.
An example of type 2 would be List. You are not in direct control of row height, you can't change the padding around views, you can't tell it to show only so many rows, etc. They don't want it to be very customizable, because then each app's lists would behave differently, defeating the purpose of a standard control.
List is designed to fill the entire parent View with as many rows as possible, even if they are empty or only partially on screen (and scroll if there are too many to show at once).
Your issue
The problem you are having comes about because the size of the List does not affect the size of its rows in any way. SwiftUI doesn't care if there are too many or too few rows to fit in your preferred size; it will happily size its rows according to content, even if it means they don't all show or there are empty rows shown.
If you really need rows to resize according to the size of their parent, you should use a VStack. If it needs to scroll, you will need to wrap the VStack in a ScrollView.
Hacky solution
If you still insist on using a list, you will have to do something like the following:
struct ContentView: View {
#State private var textHeight: Double = 20
let listRowPadding: Double = 5 // This is a guess
let listRowMinHeight: Double = 45 // This is a guess
var listRowHeight: Double {
max(listRowMinHeight, textHeight + 2 * listRowPadding)
}
var strings: [String] = ["One", "Two", "Three"]
var body: some View {
VStack {
HStack {
Text(String(format: "%2.0f", textHeight as Double))
Slider(value: $textHeight, in: 20...60)
}
VStack(spacing: 0) {
Color.red
List {
ForEach(strings, id: \.self) { item in
Text(item)
.font(.custom("Avenir Next Regular", size: 12))
.frame(height: CGFloat(self.textHeight))
.background(Color(white: 0.5))
}
}
// Comment out the following line to see how List is expected to work
.frame(height: CGFloat(strings.count) * CGFloat(self.listRowHeight))
Color.red
}.layoutPriority(1)
}
}
}
The slider is there to show how the list row heights change with the height of their child view. You would have to manually pick listRowPadding and listRowMinHeight to get the appearance that best matches your expectation. If Apple ever changes how a List looks (changes padding, minimum row heights, etc.) you will have to remember to come back and adjust these values manually.
Self size List:
If you want a List to show it's content all at once, It means you don't need the recycling feature (the key feature of the list), So all you need is to not using a List! Instead, you can use ForEach directly, then it will size itself based on it's content:
ForEach(searchService.searchResult, id: \.self) { item in
VStack(alignment: .leading) {
Text(item).font(.custom("Avenir Next Regular", size: 12))
Divider()
}.padding(.horizontal, 8)
}
You can change all sizes and spacings according to your needs
Note that You can use LazyVStack from iOS 14 to make it lazy-load and boost its performance.
Starting from iOS 14 you can use LazyVStack instead of List.
List seems to span entire parent view height independent of rows height or count.
LazyVStack {
ForEach(searchService.searchResult, id: \.self) { item in
Text(item)
.font(.custom("Avenir Next Regular", size: 12))
}
}.frame(height:
Other solution is to set .frame(height: ) on List based on rowCount*rowHeight or other GeometryReader -> geometry.size.height
SwiftUi has evolved. Here's a plain and simple answer for SwiftUI 3: https://stackoverflow.com/a/65769005/4514671