I have a list with some items.
Below the list I'd like to have to button to load more items.
(As loading all items requires some user actions like entering a TAN, this should not be done automatically when the user scrolls to the end of the list, but only if he likes to.)
What I'd like to have is a view like this:
However, if I place the List and the Button in a VStack, the Button get always displayed at the bottom of the screen, not only when I scroll to the end of the List:
struct ContentView: View {
private let items = Range(0...15).map { "Item " + String($0) }
var body: some View {
VStack {
List(items, id: \.self) { item in
Text(item)
}
HStack {
Spacer()
Button("Load more") { print("Load more items") }
Spacer()
}
}
}
}
If I add the Button to the List, the Button obviously gets displayed as a List item with a white background and without any space to the list:
struct ContentView: View {
private let items = Range(0...15).map { "Item " + String($0) }
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text(item)
}
HStack {
Spacer()
Button("Load more") { print("Load more items") }
Spacer()
}
}.listStyle(GroupedListStyle())
}
}
Is there any way to add a view that becomes visible when the user scrolls to the end of the List but that is not part of the List? (Or at least looks like being below the List and not part of it?)
You should use second variant, but a bit tuned, like below (colors/spaces modify per your needs
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text(item)
}
HStack {
Button("Load more") { print("Load more items") }
}
.listRowInsets(EdgeInsets())
.frame(maxWidth: .infinity, minHeight: 60)
.background(Color(UIColor.systemGroupedBackground))
}.listStyle(GroupedListStyle())
}
Related
I am trying to display a list of enums with a section head.
I am iterating through 2 enums to display them in the same row.
The problem I am having is that the complete list is only showing in one row instead of each individual item displaying on its own row.
Why is this happening?
struct DisplayRow: View {
var body: some View {
List(0..<flaps.allCases.count, id: \.self) {index in
HStack {
Text(flaps.allCases[index].rawValue)
.frame(maxWidth:.infinity, alignment: .leading)
Text(woundscode.allCases[index].rawValue)
}
.padding(.all)
.border(Color.gray)
}
}
struct ContentView: View {
var body: some View {
List {
Section(header: Text("Flaps")) {
DisplayRow()
}
Section(header: Text("test")) {
Text("2")
Text("3")
}
}
I placed the 2nd section just as test.
Thanks in advance.
screenshot
I have a SwiftUI Picker in which an item is selected. The text of one element can be large, so I used UIKit UIPickerView and set the manual height to 100, but at some point it became not enough. Is it possible to make scrolling horizontal for each element?
I want to get something like this:
Picker("Items", select: self._selectItem) {
ForEach(self.items, id: \.self) { item in
ScrollView(.horizontal, showsIndicators: false) {
Text(item.description)
}
.tag(item)
}
}
That should work fine. If you only want to scroll one item, you would have to insert a check of the item length.
let items = [
"A long item text.",
"And a even longer item text which is really going further.",
"Another item text which is really going further."
]
struct ContentView: View {
#State private var select = ""
var body: some View {
VStack {
Text("Make your selection!")
List(items, id: \.self) { item in
ScrollView(.horizontal) {
Text(item)
}
.listRowBackground(item == select ? Color.red : Color.white)
.onTapGesture {
select = item
}
}
}
}
}
I would strongly suggest to separate the picking from the text display and scrolling, e.g. like this:
struct ContentView: View {
#State private var select = items[0]
var body: some View {
VStack {
Text("Make your selection!")
Picker("Items", selection: $select) {
ForEach(items) { item in
Text(item.title)
.tag(item)
}
}
ScrollView {
Text(select.text)
}
.padding()
.frame(height: 200)
}
}
}
I'm trying to accomplish what I thought was a relatively simple thing, which is to have a view show fullscreen when you tap on an item in a list.
The code shown here works more or less. However, I have to tap exactly on the text. Tapping the free area does not trigger the onTap event.
Sample code:
struct ItemListView: View {
var items: [Item]
var titel: String
#State private var selectedItem: Item?
var body: some View {
List(items) { item in
ItemListCell(item: item)
.onTapGesture {
selectedItem = item
}
}
.navigationBarTitle(titel)
.fullScreenCover(item: $selectedItem, onDismiss: nil) { item in
ItemDetailView(item: item)
}
}
}
struct ItemListCell: View {
var item: Item
var body: some View {
Text(String(item.id))
Text(item.name)
}
}
struct ItemDetailView: View {
#Environment(\.presentationMode) var presentationMode
var item: Item
var body: some View {
Text(String(item.id))
Text(item.name)
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Label("Close", systemImage: "xmark.circle")
}
}
}
I also looked at the "Supporting Selection in Lists" chapter in the documentation, but that only works when the list is in edit mode.
Is there a swiftUI equivalent of UIKit's tableView(_:didSelectRowAt:)?
Make sure that the frame of the content is taking up the whole cell. I do this in the sample below by using .frame(maxWidth: .infinity, alignment: .leading).
Then, add a contentShape modifier sot that SwiftUI knows to consider the entire shape to be the content -- otherwise, it tends to ignore the whitespace.
struct ItemListCell: View {
var item: Item
var body: some View {
VStack {
Text(item.id)
Text(item.name)
}
.frame(maxWidth: .infinity, alignment: .leading)
.border(Color.green) //just for debugging to show the frame
.contentShape(Rectangle())
}
}
Alternatively, if you use Button instead of onTapGesture, you basically get this all for free:
List(items) { item in
Button(action: {
selectedItem = item
}) {
ItemListCell(item: item)
}
}
Lastly, no, there is no direct equivalent of tableView(_:didSelectRowAt:)
As part of a bigger Form I'd like to have a Section with a List whose entries can be reordered.
In the whole view it should be only possible to edit that particular list, nothing else. Therefore I'd like to have the EditButton near the List.
If I just set the EditButton as the Section header, reordering List items works but I can't display a section title:
struct ContentView: View {
private let items = Range(1...4).map { "Item " + String($0) }
var body: some View {
Form {
Section(header: EditButton()) {
ForEach(items, id: \.self) { item in
Text(item)
}
.onMove(perform: reorderItems)
.onDelete(perform: deleteItems)
}
}
}
func reorderItems(from sourceIndices: IndexSet, to destinationIndex: Int) { /* ... */ }
func deleteItems(at offsets: IndexSet) { /* ... */ }
}
But if I wrap the EditButton in a HStack to display the button on the right, a tap on "Edit" changes the button's title to "Done" but doesn't start List's edit mode anymore:
struct ContentView: View {
#Environment(\.editMode) var editMode
private let items = Range(1...4).map { "Item " + String($0) }
var body: some View {
Form {
Section(header: HStack {
Text("Section title")
Spacer()
EditButton()
}.environment(\.editMode, self.editMode)) {
ForEach(items, id: \.self) { item in
Text(item)
}
.onMove(perform: reorderItems)
.onDelete(perform: deleteItems)
}
}
}
func reorderItems(from sourceIndices: IndexSet, to destinationIndex: Int) { /* ... */ }
func deleteItems(at offsets: IndexSet) { /* ... */ }
}
I also tried, as the code shows, passing the editMode environment variable to the HStack, but nothing helped.
Is there any way to get the EditButton inside an HStack working?
(Remark: As the List is part of a bigger Form, placing the EditButton out of the Section as suggested here is not an option in my case.)
Here is working solution - looks like they require that EditButton was a root view of section, so we can construct everything else above it. (tested with Xcode 11.4 / iOS 13.4)
Note: #Environment(\.editMode) var editMode is not needed
Section(header:
EditButton().frame(maxWidth: .infinity, alignment: .trailing)
.overlay(Text("Header"), alignment: .leading)
)
{
ForEach(items, id: \.self) { item in
Text(item)
}
.onMove(perform: reorderItems)
.onDelete(perform: deleteItems)
}
I am trying to remove the "sectionStyle" for my List rows. In order to achieve a tableview-like view, you would write the following:
List(items, id: \.id) { item in
ItemRow(with: item)
}
If you want to interpret tap events on each row, you would wrap the ItemRow into a Button:
List(items, id: \.id) { item in
Button(action: rowTapped) {
ItemRow(item: item)
}
}
The interesting part is that when the List knows about the Button, it will automatically give a selection animation like tableView's selectionStyle. For my application, I'd like to not have that. Is there a way to set the "selectionStyle" for the List to "none"?
There are a number of List behaviours, that I haven't been able to alter. It's quite easy to build your own list though if you have specific needs different than the system List view. Here's a simple example that I've used.
struct MyListView:View {
var body: some View {
ScrollView {
ForEach(items, id: \.id) { item in
VStack {
Button(action: self.rowTapped) {
ItemRow(item: item)
}
Divider()
}
}
}.padding()
}
func rowTapped() {
print("rowTapped")
}
}
struct ItemRow:View {
var item:Item
var body: some View {
HStack {
Text(item.name)
Spacer() // force text to left justify
}
}
}