How to create a SwiftUI List View with a large (all list) header view? - uitableview

I would like to create a List view in SwiftUI with has a large, header which has no paddings and extends into the safe area.
For example like in this screenshot:
Solving this in UIKit would not be problem. One could simply use the tableViewHeader of UITableView to achieve this. But how can this be done in SwiftUI using a List view?
While I found several examples on how to create section headers, I found nothing about a complete table header.
Of course one can simple add a subview to the list:
List {
// Header
Rectangle()
.someStyling(...)
// Items
ForEach(myList, id: \.self) { element in
Text(element)
}
}
However, the same paddings, safe area insets, etc. which are applied to the list items are also applied to the header. Can this be avoided?
Or is List not necessary?
I took this screenshot from this YouTube tutorial video. Unfortunately the tutorial does not show exactly what I am looking for. Instead of using a List to show large data set, it simply uses a ScrollView holding a VStack with an Image view (= the header) + a ForEach loop for the items.
Coming from UIKit and UITableView I have my doubts, that this is a good idea. Creating thousands of UIViews in a loop and stacking them would result in terrible performance. UITableView on the other hands re-uses view to create a much better performance.
But is this also true for List vs. VStack in SwiftUI?
Or is ListView just the same as a VStack and creating a large number of subviews in a loop is no problem here (e.g. because SwiftUI always only creates/draws the visible views)?

The thing about SwiftUI creating only the visible views is only true for LazyVStack, not VStack.
For the header in the List, you can use .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)). However, there would still be some spacing left.
You can use .padding(.all, -20) on the List, but then you would need to revert the padding on all of its elements. It’s a long shot, but could be worth it.

Related

Drag and drop of multiple items between views with SwiftUI

I would like to implement drag and drop of multiple items (rows) in SwiftUI between two different views on iPhones, while also keeping reorder between sections. Currently I am using LazyVStack for it, but through .onDrag modifier only one NSItemProvider can be returned, which means that only one item can be dragged at a time. I also tried using Lists for this, but with Lists it is not possible to implement drag and drop between different List sections.
Is there any way to pass multiple NSItemProvider items into .onDrag modifier? Or any other way to implement drag and drop of multiple items between two views, while also keeping reorder between sections. I know this all is possible with UIKit (UICollectionView), but I am looking into opportunities of making it work with SwiftUI.
I've done a lot of research but haven't found any solution that would make it all work in SwiftUI, so decided to also ask here as the last resort. Thank you, any feedback would be much appreciated.

Are LazyH & V grids performant for lots of data?

Apple’s documentation says that for the LazyVGrids and LazyHGrids that items are created only as needed. Are they disposed of when needed? Am wondering specifically if they are a performant SwiftUI version of collection views that could be used for production. Thank you.
They are performant, especially compared to just using a VStack. However - the cells aren't disposed of and recreated later, dequeuing like you might know of in UIKit. The lazy loading only helps for when the rows are first rendered, which most of them won't be.
You can test this with just making a really massive list of views, where a random number gets generated each time the view is created:
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack {
ForEach(0 ..< 100000) { i in
ForEach(0 ..< 100) { j in
if j == 0 {
Text("SPACER: \(Int.random(in: 1 ... 100))")
} else {
Text("index: \(j) - \(i)")
}
}
}
}
}
}
}
Once these random numbers are generated, you can see they don't refresh or change after scrolling from the top to the bottom.
This list has 10 million items, which is orders of magnitude more than you want to display. Although it does run a bit slowly, it is still on the verge of usable. From my testing this example when scrolling from top to bottom used about 400MB of memory, far more than you want to ever use.
I think LazyVStack is perfectly good for almost all use cases. You should usually try limit the number of items anyway - since you can always create a paging system if needed, like an automatic loading of more views when you scroll to the bottom.
This is a stress-test, and you should not have near this many rows in a real use case.

How to make a UICollectionView read its own accessibility label before reading out its first cell's label?

Presently, I have a container view holding a UICollectionView, which has a number of accessibility-enabled cells.
While using VoiceOver, I can cycle through all the elements in the app, and when I get to my container view, it will cycle through all the cells and their labels (I don't have any accessibility methods or properties enabled on my container view).
However, I'd like it so that I can assign an accessibility label to the UICollectionView so that VO reads the UICollectionView's accessibility label FIRST, before it reads the first cell's accessibility label.
ie,
Button immediately above CollectionView's label: "Button"
CollectionView's label: "Filter Bar"
Cell 1's (default selected) label: "Sports"
Cell 2's label: "Chevrolet"
Scenario:
When the user has Button selected, and swipes to the right, the first cell in the table view gets selected.
Expected Result:
VO reads: "Filter Bar, selected, Sports, button"
Actual Result:
VO reads: "selected, Sports, button".
Is it possible to make VO read the accessibility label of the collection view first?
A UICollectionView may be seen as a container inside which many elements are embedded (its cells) and, as is, you can't have simultaneously a parent view (the collection view) and its child views (its cells) that are both accessible with VoiceOver: either the collection view can be selected or its cells.
Is it possible to make VO read the accessibility label of the collection view first?
Following the explanation above, you can't with the present configuration.
However, a solution may be to create a UIAccessbilityElement that wraps your collection view with the simple label you want to be read out: this element will be selected before the collection view cells are in turn.
Finally, I strongly recommend to take a look at the demo inside this detailed WWDC video that may provide some useful tips for your use case.
⚠️ ⬛️◼️🔳▪️ EDIT ▪️🔳◼️⬛️ ⚠️ (after MarkS's comments)
I'm trying to figure out a way to wrap it, but so far I've been unsuccessful.
Try this code snippet hereafter to create your accessibility element:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let a11yWrapper = UIAccessibilityElement(accessibilityContainer: view!)
a11yWrapper.accessibilityFrameInContainerSpace = CGRect.init(x: 40.0,
y: 40.0,
width: 100.0,
height: 50.0)
a11yWrapper.accessibilityLabel = "Filter Bar"
a11yWrapper.accessibilityTraits = .staticText
view.accessibilityElements = [a11yWrapper]
}
}
Adapt it to your app environment and that should make VO read the accessibility label of the collection view first.
There aren't any examples in the documentation which specifically go over this situation.
I think it's because your design shouldn't be done this way.
Usually, it's either the collection view itself or its cells that are accessible, not both as explained earlier.
However, according to what you exposed in your post, you could use the custom actions to reach your purpose as well: each action will correspond to a particular cell... that's definitely not perfect but that's as close as you can get to what you’re asking.

Creating a menu in iOS

I'm currently creating an update of my iOS application and I'm a bit stuck. I've tried to googling around but cannot find a decent answer on this.
I've a menu which links to different views. And I'm not really sure if I've done it the best method.
I've created a view, and added the links into a stack view. Should I considering changing it all to a tableview? or a collection view? Or maybe there's another way?
The current look:
Should I change this to a tableview? collection view? or something else? Or just let it stay as it is?
If the number of items in your menu changes at runtime and is large, you should use a table view, because a table view is good for efficiently displaying a screen's worth of items from a large list of items.
If the contents of your menu is small (under maybe two screenfuls of items) and fixed at compile time and you are using a storyboard, then you could use a table view with static cells, if you can make it look the way you want.
If the contents of your menu is small, then you can use a stack view (inside a scroll view) if that is easier for you. There is no particular advantage to using a table view over a stack view to display a small amount of content, unless you need other features of the table view (like the ability to select/deselect rows).
Based on the screen shot you posted, I'd either use a table view with static cells (since the screen shot is from a storyboard) or a stack view, depending on whether I can get the appearance I want from a table view. If, as in the screen shot, the buttons must be centered vertically, I'd use a stack view, because it's easier to vertically center the content with a stack view.
Look, the fact of have many itens on your screen is clear on the mobile applications, to make it easy, we have collecions view like UITableView and UICollectionView. On the UITableView's case, this implements the scrolling and have methods do handle the operations' list, you can see the documentation to check these methods: https://developer.apple.com/documentation/uikit/uitableview.
Main reasons to use UITableView
Implements scroll behavior.
Independent of size screen you can access all itens.
Easy to detect interactions like tap on cell.
Easy to make changes, like insert and remove content.
The UITableView exists precisely to solve problems like you has.

Nested collection view in table view is better or multiple collection view

I have to showcase some image with a little information like title and subtitle which will be horizontally swipeable. Basically, it will be like Airbnb app which shows the category and elements in it with horizontal swipe. The only difference is that in my case number of category(row) is fixed, which is 2. DataSource is same for both rows. I have two approach first is using two CollectionView and second one is by using a TableView and nesting the CollectionView inside tableview cell. Googled and get only the implementation part for both approach not the comparison or use cases for the approach. So my question here is, which process should I follow and why?
1) Using nested Collection view will make the code complex.Generally used only when cannot be implementable using table view.
2) Table view is similar to collection view just that it can be scrollable vertical not horizontal. This make table view easy to implement.
3) In your case collection in table view will work great. Row is fixed in that case you can use static table view.

Resources