Using SwiftUI, I have a List of items retrieved from CoreData. Everything I read makes it look like the list should be something I can scroll normally, but in both the simulator and running on an iPad, the List doesn't scroll.
Clearly there is some piece missing, but I can't seem to find what it is. The list populates correctly, but it simply won't scroll.
struct PeopleList : View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(
entity: Person.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Person.lastName, ascending: true),
NSSortDescriptor(keyPath: \Person.firstName, ascending: true)
]
) var people: FetchedResults<Person>
var body: some View {
ZStack(alignment: .topLeading) {
List(self.people, id: \.self) { person in
Text(person.descriptionForList())
}
}
.frame(width: 400, height: 200)
.modifier(RoundedEdge(width: 4, color: Color(red: 0.6, green: 0.6, blue: 0.6), cornerRadius: 10))
.padding(10)
}
}
Things that didn't change anything:
Removing the ZStack { }
Replacing the List with a simple List(0...100, id: \.self) { item in Text("hey \(item)") } -- still won't scroll.
Adding a .frame() to the List() itself.
(added) removing the .frame .modifier and .padding all accomplished nothing.
This is not about (the following topics have Q&A's addressing them, but not this issue):
programmatic scrolling
disabling scrolling
scroll indicators
ScrollView
UIKit
How can I track down what might be preventing the List() from scrolling?
Turns out, the issue wasn't with anything in the struct that was posed above; it all had to do with the View where that struct was being used. Specifically, when a Color() -- even of .opacity(0) -- was in a ZStack sitting "above" the List(), the latter stops scrolling. Displays fine, but just won't scroll.
This question describes the same thing happening, albeit in somewhat different circumstances.
I'll leave this question up, since somebody else may be at the same place I was, "why isn't my List() scrolling?", rather than "why isn't my List() in a ZStack scrolling?" Hopefully a version of Swift later than 13 will fix this behavior!
Related
I'm dipping my toe into SwiftUI and WatchOS for the first time. I'm making good progress, but I can't figure out how to get rid of the black "gutters" on either side of my Image controls. I've tried setting all the backgrounds to white, but the gutter persists.
What property on which view do I need to set to change the color of the gutters to match the background?
SwiftUI
struct ContentView: View {
var body: some View {
List {
Image("cat-1").resizable().scaledToFill().background(Color.white)
Image("cat-2").resizable().scaledToFit().padding(5).background(Color.white)
Image("cat-3").resizable().scaledToFit().padding(.top, 5).background(Color.white)
}.background(Color.white).listStyle(CarouselListStyle())
.background(Color.white)
}
}
Try adding a
.listRowPlatterColor(.clear)
put it inside the list like this...
struct ContentView: View {
var body: some View {
List {
Image("cat-1")
.resizable()
.scaledToFit()
.listRowPlatterColor(.clear)
Image("cat-2").resizable().scaledToFit().padding(5).background(Color.white)
Image("cat-1")
.resizable()
.scaledToFit()
.padding(.top, 5)
.background(Color.white)
}
.listStyle(CarouselListStyle())
}
}
I put it in the first item and left it off of the second and third so that you could see the difference. This other question can provide some more details:
How to style rows in SwiftUI List on WatchOS? .
You should then be able to style it however you like.
I want to create a SwiftUI List, but not show scroll indicators. ScrollView offers showsIndicators to do this. How can it be done?
Any Indicators (List, scrollView, etc.)
you can get rid of showing indicators for all Lists, but with an API of the UITableView. because SwiftUI List is using UITableView for iOS behind the scene:
struct ContentView: View {
init() {
UITableView.appearance().showsVerticalScrollIndicator = false
}
var body: some View {
List(0...100, id: \.self) { item in
Text("hey")
}
}
}
Note that this will eliminate all TableViews and Lists indicators. You should make it visible again if you need to.
⚠️ Not Yet Important Note
Seems like Apple is removing appearance hacks (but not for this one yet). So you can use LazyVStack inside and ScrollView instead of List and use the available argument for hiding the indicators.
struct ContentView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false) { // <- This argument
LazyVStack {
ForEach(1...100, id: \.self) {
Text("\($0)").frame(height: 40)
}
}
}
}
}
It is actually easy to accomplish this without any appearance work arounds in the answer accepted. You just have to use the ScrollView initializer and set the parameter showsIndicators to false.
ScrollView(.vertical, showsIndicators: false) {
// ... your content for scrollView
}
Inside your ScrollView you could use LazyVStack, if you have a lot of subviews to scroll through. SwiftUI will then render very efficiently your subviews: "lazy" -> only if needed).
Until there is a native SwiftUI way to achieve this, the Introspect library provides a decent solution.
After applying all modifiers to your list just add as a last modifier the following:
List {
...
}
.introspectTableView { tableView in
tableView.showsVerticalScrollIndicator = false // here you can access any other UITableView property
}
I hope there is a native way to do that at some point.
Hide scrolling indicator now became very simple
List {}.scrollIndicators(ScrollIndicatorVisibility.hidden)
List basically creates a tableview (UpdateCoalescingTableView) behind the scenes, and tableview's are scrollable. Unfortunately, however, you can't get to the scrollview attributes in SwiftUI.
You "might" be able to create a UIViewRepresentable that could walk up the view hierarchy until it finds a scrollview, but I wouldn't recommend it.
You could also create your own scrollview, put a vstack inside it, and "fake" a list view, which would probably be the safer approach.
The choosen answer won't work in iOS 16. They released a new viewModifier called .scrollIndicators(.hidden). I created a viewModifier wrapper which you can call like this on your List: .modifier(HideListIndicatorsViewModifier())
struct HideListIndicatorsViewModifier: ViewModifier {
#ViewBuilder
func body(content: Content) -> some View {
if #available(iOS 16.0, *) {
content
.scrollIndicators(.hidden)
} else {
content
}
}
}
swift scrollview hide scrollbar
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
hide scroll view indicators bar swiftui
ScrollView(.vertical, showsIndicators: false) {
// ... your content for scrollView
}
I cannot understand why in the simulator the layout is different from the layout displayed in xcode/preview.
struct ContentView : View {
var body: some View {
ZStack(alignment: .bottom) {
ARViewContainer()
Text("hello")
}
}
}
here the screenshots:
TLDR; I don't know why your simulator and preview don't match, but I do know why it's appearing the way that it is on the device. Are you getting any errors in the debug?
Any container views in SwiftUI will only take up the required space that they need. they will also distribute according to your settings. For example, you have a ZStack that contains a bottom alignment. You also have a ARViewContainter() that takes up a portion of that stack. They are aligned behind each other on the Z axis where the text is in front and the other container is behind. A quick way to prove this and test it is to include a background shape behind everything for example.
var body: some View {
ZStack {
Rectangle().edgesIgnoringSafeArea(.all)
.foregroundColor(Color.red)
//Your Views
}
}
This will force the ZStack to take up all available space and your other views should then align as expected. Basically, your Text is aligning to the bottom of the maximum provided space, which is being provided by your ARViewContainer()
Further Reading and Understanding
Views only take up the space required for what's in them, unless otherwise specified.
ZStacks operate on the Z axis, forward/backwards.
In your case you have a view with a set size called ARViewContainer() which takes up the width of the screen and a portion of the height. Since it's the largest view you have, the ZStack inherits that size.
Your text is smaller than the ZStack so the ZStack does NOT inherit the size. You do however have a .bottom assignment. So your text is over your ARViewContainer() and aligned to the .bottom edge of that container.
Finally the ZStack is centered in the remaining space available, giving it the impression that your .bottom isn't doing anything, when in reality it is.
Reproducing the Issue
Here is a code snippet that reproduces your issue and makes it a bit clearer and easier to understand.
var body: some View {
ZStack(alignment: .bottom) {
Rectangle().foregroundColor(Color.yellow).frame(width: 100, height: 100, alignment: .center)
Text("Test")
}
}
This is my example that I am trying to get to work:
struct ContentView: View {
let links = ["Item 1", "Item 2", "Item 3", "Item 4"]
var body: some View {
NavigationView {
ScrollView {
Text("My Title")
List(links, id: \.self) {
link in
NavigationLink(destination: TestView()) {
Text(link)
.padding(.vertical, 4)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.frame(height: 178)
Text("Some more content here")
}
}
}
}
Note: TestView is just some view with the text hello world on it.
I am trying to copy Apple Music's style of navigation. I tried putting a Button in the NavigationLink but tapping it on the text wouldn't change views, and I couldn't find a way to reliably change the color of the row when tapped, at the same time. Also in some approach, I managed to make it work, but the way the colors animate is different, i.e. it fades from A to B, over ~100ms whereas what I'm trying to achieve is to animate between the states instantly (like in Apple Music).
My current approach is using a List, putting NavigationLinks inside it and then cutting off the whole view by giving it a height. This way I can put it alongside other content.
It's working fine for now, but whenever I click on an row and go back, the row is still highlighted, when it shouldn't. Is there a way to make it so that it deselects when going back to the screen somehow?
I think this bug is being caused by the List being inside a ScrollView, since when I removed ScrollView, the list worked properly, and there wasn't this highlight bug. But I need to be able to put my content with the list, and I don't intend to have a list take up the whole screen.
Is there any way to fix this bug with this approach? I'm also willing for other ways to achieve the same result without using List.
Trying to use ForEach instead ofList?
With a view for row (CustomRow) where you can pass link item and set custom dividing line, background etc ...
ForEach(links, id: \.self) { link in
NavigationLink(destination: TestView()) {
CustomRow(item: link)
}
}
.frame(height: 178)
I want to create a SwiftUI List, but not show scroll indicators. ScrollView offers showsIndicators to do this. How can it be done?
Any Indicators (List, scrollView, etc.)
you can get rid of showing indicators for all Lists, but with an API of the UITableView. because SwiftUI List is using UITableView for iOS behind the scene:
struct ContentView: View {
init() {
UITableView.appearance().showsVerticalScrollIndicator = false
}
var body: some View {
List(0...100, id: \.self) { item in
Text("hey")
}
}
}
Note that this will eliminate all TableViews and Lists indicators. You should make it visible again if you need to.
⚠️ Not Yet Important Note
Seems like Apple is removing appearance hacks (but not for this one yet). So you can use LazyVStack inside and ScrollView instead of List and use the available argument for hiding the indicators.
struct ContentView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false) { // <- This argument
LazyVStack {
ForEach(1...100, id: \.self) {
Text("\($0)").frame(height: 40)
}
}
}
}
}
It is actually easy to accomplish this without any appearance work arounds in the answer accepted. You just have to use the ScrollView initializer and set the parameter showsIndicators to false.
ScrollView(.vertical, showsIndicators: false) {
// ... your content for scrollView
}
Inside your ScrollView you could use LazyVStack, if you have a lot of subviews to scroll through. SwiftUI will then render very efficiently your subviews: "lazy" -> only if needed).
Until there is a native SwiftUI way to achieve this, the Introspect library provides a decent solution.
After applying all modifiers to your list just add as a last modifier the following:
List {
...
}
.introspectTableView { tableView in
tableView.showsVerticalScrollIndicator = false // here you can access any other UITableView property
}
I hope there is a native way to do that at some point.
Hide scrolling indicator now became very simple
List {}.scrollIndicators(ScrollIndicatorVisibility.hidden)
List basically creates a tableview (UpdateCoalescingTableView) behind the scenes, and tableview's are scrollable. Unfortunately, however, you can't get to the scrollview attributes in SwiftUI.
You "might" be able to create a UIViewRepresentable that could walk up the view hierarchy until it finds a scrollview, but I wouldn't recommend it.
You could also create your own scrollview, put a vstack inside it, and "fake" a list view, which would probably be the safer approach.
The choosen answer won't work in iOS 16. They released a new viewModifier called .scrollIndicators(.hidden). I created a viewModifier wrapper which you can call like this on your List: .modifier(HideListIndicatorsViewModifier())
struct HideListIndicatorsViewModifier: ViewModifier {
#ViewBuilder
func body(content: Content) -> some View {
if #available(iOS 16.0, *) {
content
.scrollIndicators(.hidden)
} else {
content
}
}
}
swift scrollview hide scrollbar
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
hide scroll view indicators bar swiftui
ScrollView(.vertical, showsIndicators: false) {
// ... your content for scrollView
}