I have a database. I want to display the content of the database.
The most obvious solution:
#State var items = Array(1...100)
List(items, id: \.self) {
Text("Item \($0)")
}
But what if I have tens/hundreds of thousands of records? What is the base approach for lazy loading?
In SwiftUI 2.0 have LazyVStack and LazyHStack for your purpose, take a look https://developer.apple.com/documentation/swiftui/lazyhstack
Related
I'm new to programming in SwiftUI. I created a list of a custom view that expands on tap. However, whenever I tap the custom view, the height of the list row doesn't update. Here's what my code looks like:
List(){
ForEach(ingredients){ ingredient in
IngredientList(ingredient: ingredient)
}
}
struct IngredientList: View {
let ingredient: Ingredient
#State private var showBody = true
var body: some View {
Hstack{
VStack(alignment: .leading){
Text("Text 1")
if(showBody){
Divider()
Text("Text 2")
.font(.caption)
}
}
}
.contentShape(Rectangle())
.onTapGesture {
showBody.toggle()
}
}
}
I think it has something to do with state of the list but I can't seem to figure it out.
I found an ugly but working solution. On the expanded item change, check whether it is a selection mode, remember the current data set, clear it, and set it back with DispatchQueue.main.async. It works on iOS 15.4.1. I hope Apple will fix the issue in the subsequent releases.
Update: try introspect. You will be able to retrieve the underlying table view and call UITableView.reloadData() on the expanded row change
Is there anyway to keep the tab bar showing while presenting a modal / sheet view?
Here is a minimal failing example.
import SwiftUI
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
Button("Press to dismiss") {
dismiss()
}
.padding()
}
}
struct Tab1: View {
#State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}
struct MainView: View {
var body: some View {
TabView {
Tab1()
.tabItem {
Label("Tab 1", systemImage: "heart")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
Thanks for answering my question in the comments.
Unfortunately the standard means of presenting views in SwiftUI is that they are truly modal – they capture the whole interaction context for the current scene, and you can’t interact with anything else until the modal is dismissed.
This is also the case for iPadOS. Even though a modal presented with .sheet on an iPad allows much more of the underlying view to be visible, you can’t interact with it until the sheet disappears. You can interact with different parts of the app by running two scenes side-by-side in split screen mode, but each half is a separate scene and any presented sheets are modal for that scene.
If you want one tab to optionally present a view over its usual content but still allow access to the tab view and its other tabs, that’s not a modal context and SwiftUI’s built-in sheet won’t work. You will have to implement something yourself - but I think that’s doable.
Rather than using .sheet, you could optionally add an overlay to your Tab1 view, using the same boolean state variable showingSheet. In this approach, the default dismiss environment variable won’t be available, so passing in the state variable as a binding value would be an alternative:
var body: some View
<main display>
.overlay(showingSheet ? Sheet1(presented: $showingSheet) : EmptyView())
You might also find that a ZStack works better than .overlay depending on what the contents of the tab view actually are.
You’ll definitely have a lot more structural work to do to make this work, but I hope you can see that it’s possible.
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 want to use the awesome MultiSegmentPicker written by Yonat Sharon in my SwiftUI View.
https://github.com/yonat/MultiSelectSegmentedControl
However, I don't fully understand the interaction between the UIViewRepresentable View and my SwiftUI View. How do I get the host view controller to shrink its height down to the size of the segmented control?
Here's the debugger view of the demo page - notice the blue area around the top bar:
The demo code doesn't give a lot of insight into the issue, it's just a call to the UIViewRepresentable view. I've simplified it to just one example here:
struct MultiSegmentPickerX: View {
#State private var selectedSegmentIndexes: IndexSet = []
var body: some View {
VStack(alignment: .center) {
Spacer()
MultiSegmentPicker(
selectedSegmentIndexes: $selectedSegmentIndexes,
items: ["First", "Second", "Third", "Done"]
)
}
}
}
Notice I have a VStack with a Spacer() before the control.
The desired behavior for this example would be that the bar with "First", "Second", etc. would be snug against the bottom of the screen. Instead the Host Controller holds onto all that space...
Do I need to use Geometry reader to solve this issue and shrink the height down. Or do I have to adjust something in the UIViewRepresentable View?
Any insights into bridging UIKit and SwiftUI are always appreciated...
Is this an easy fix anyone?
These did not solve my issue:
UIViewRepresentable content not updating
How do I make SwiftUI UIViewRepresentable view hug its content?
How do I size a UITextView to its content?
Cannot test it now, just by thoughts, try with fixed size as below
MultiSegmentPicker(
selectedSegmentIndexes: $selectedSegmentIndexes,
items: ["First", "Second", "Third", "Done"]
).fixedSize() // << here !!
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
}