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)
}
}
}
Related
When using a NavigationView and a ScrollView with searchable, as soon as you focus a item in the LazyVGrid the search bar collapses the keyboard, and it's no longer possible to re-focus the search bar to change the query.
It doesn't matter if the .searchable modifier is applied to the ScrollView or the NavigationView.
The more I look at it, the more it appears to be a SwiftUI bug on tvOS, but I would still like to find a workaround, if possible.
Sample code which reproduces the problem:
import SwiftUI
struct ContentView: View {
private var fruits = ["Apples", "Pears", "Oranges", "Plums", "Pineapples", "Bananas"]
#State private var items: [String]
#State private var searchText: String = ""
init() {
self.items = fruits
}
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 300))], spacing: 40) {
ForEach(items, id: \.self) { item in
NavigationLink(destination: DetailView(text: item)) {
Text(item)
}
}
}
}
.searchable(text: $searchText)
.onChange(of: searchText) { query in
if query.isEmpty {
items = fruits
} else {
items = fruits.filter { $0.contains(query) }
}
}
}
}
}
struct DetailView: View {
let text: String
var body: some View {
Text(text)
}
}
Gif illustrating the problem:
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:)
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())
}
I'm pretty sure this is a bug in SwiftUI, but I wondered if anyone has encountered it and figured out a workaround. My normal use case is to have a search field appear, but I've simplified it to the point where a simple text string exhibits the bug.
Create a single-view app, copy this into ContentView, and run it. Tap the search icon twice, then scroll the view; you'll see the text scrolling UNDER the title.
import SwiftUI
struct ContentView: View {
private var items = (0 ... 50).map {String($0)}
#State private var condition = false
var searchButton: some View {
Button(action: {self.condition.toggle()}) {
Image(systemName: "magnifyingglass").imageScale(.large)
}
}
var body: some View {
NavigationView {
VStack {
if condition {
Text("Peekaboo")
}
List {
ForEach(items, id: \.self) {item in
HStack {
Text(item)
}
}
}
}
.navigationBarTitle("List of Items")
.navigationBarItems(leading: searchButton)
}
}
}
Maybe it is a bug, submit feedback to Apple, but currently this is how NavigationView behaves - it collapses navigation bar only if its top content is List/ScrollView/Form. So to solve the issue move your VStack either into a List or out of NavigationView
1)
var body: some View {
NavigationView {
List {
if condition {
Text("Peekaboo")
}
ForEach(items, id: \.self) {item in
2)
var body: some View {
VStack {
if condition {
Text("Peekaboo")
}
NavigationView {
List {
It seems that a View cannot cope with variable number of views.
A workaround this strange behavior is this:
import SwiftUI
struct ContentView: View {
private var items = (0 ... 50).map {String($0)}
#State private var condition = false
var searchButton: some View {
Button(action: {self.condition.toggle()}) {
Image(systemName: "magnifyingglass").imageScale(.large)
}
}
var body: some View {
NavigationView {
VStack {
if condition {
Text("Peekaboo")
} else {
Text("")
}
// or use this Text(condition ? "Peekaboo" : "")
List {
ForEach(items, id: \.self) {item in
HStack {
Text(item)
}
}
}
}
.navigationBarTitle("List of Items")
.navigationBarItems(leading: searchButton)
}
}
}
Let me know if it works, if not let us know what device/system you are using. Tested with Xcode 11.6 beta, Mac 10.15.5, target ios 13.5 and mac catalyst.
I have such VStack with list inside it
VStack(alignment: .leading, spacing: 16) {
Text("Contacts")
.font(.custom("AvenirNext-DemiBold", size: 20))
.foregroundColor(Color("DarkTitle"))
.padding(8).layoutPriority(1)
List(self.contacts) { contact in
ContactOption(contact: contact)
.padding(.horizontal, 4)
} //.frame(height: 240)
}
The problem with this code is that List tries to expand content as much as it can here taking up entire screen in spite of having just 4 contacts.
I can set this height to fixed value using frame(height: 240)
I consider wether there is possibility to enforce List to wrap its content like Text() view does.
i.e. if there is 4 rows in List wrap content to display just this 4 rows, if there is 8 rows expand to this 8 rows. Then I could set some max height ex. 400 above which List could not expand anymore and then it will be scrollable.
ok, i tried a bit and i am not sure whether you can use it or not, but check this out: (just tap on add and remofe to see how the list gets bigger and smaller)
struct ContactOption : View {
var contact: String
var body: some View {
Text(contact)
}
}
struct ListView : View {
var contacts: [String]
var body : some View {
// List(self.contacts, id: \.self) { contact in
// ContactOption(contact: contact)
// .padding(.horizontal, 4)
// }
List {
ForEach (contacts, id: \.self) { contact in
Text (contact)
}
}
}
}
struct ContentView: View {
#State var contacts = ["Chris", "Joe", "Carla", "another"]
var body: some View {
VStack() {
HStack {
Button("Add") {
self.contacts.append("dust")
}
Button("Remove") {
self.contacts = self.contacts.dropLast()
}
}
Text("Contacts")
.foregroundColor(Color.blue)
.padding(8).layoutPriority(1)
Form {
ListView(contacts: contacts)
Section(footer: Text("hi")) {
Text("hi")
}
}
Divider()
Text("end list")
.foregroundColor(Color.orange)
}
}
}