How to make ScrollView refreshable swiftui? - swiftui

With Swiftui 3 Apple added modifier refreshable. I can use it with List. But if I have scrollview it doesn't work. Any ideas?
ScrollView(.vertical) {
LazyVGrid(columns: columns, spacing: gridSpacing) {
ForEach($products, id: \.self) { product in
CardView()
}
}
}.refreshable {
}

Related

Unexplained Gap in SwiftUI View

I am seeing a white line between two of my view elements, that I cannot explain.
The following cod is the main SwiftUI View
var body: some View {
NavigationView {
VStack {
TextField("Filter", text: $lastNameFilter)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding([.top, .leading, .trailing, .bottom])
.background(Color(UIColor.systemGroupedBackground))
FilteredList(filter: lastNameFilter)
} }
The FilteredList view is very simple:
var body: some View {
List {
ForEach(fetchRequest, id: \.self) { recipient in
NavigationLink(destination:
ViewEventsView(recipient: recipient)) {
Text("\(recipient.wrappedFirstName) \(recipient.wrappedLastName)")
.foregroundColor(.green)
}
}
.onDelete(perform: deleteRecipient)
}
}
I have tried with and without padding, but that is not the issue. The .padding, is adjusting the inset of the "filter" TextField.
Any pointers would be appreciated.
That's probably the default spacing of the VStack. Try changing it to VStack(spacing: 0).

How to recreate the grid (in screenshot) in SwiftUI without breaking navigation?

I am trying to recreate a layout similar to the Reminders app. Looking at it makes me think it was built with SwiftUI. I also believe Apple mentioned so in one of the WWDC videos (can't remember which one).
This above screenshot seems to be a List, with a LazyVGrid as the first View inside the List. Tapping on each of the items in the LazyVGrid, such as Today, Scheduled, All and Flagged, navigates to the relevant screen, which means they are all NavigationLinks. Also note that the LazyVGrid has 2 columns.
And then there is another section "My Lists" which has rows which look like regular list rows in a List with style .insetGrouped. Also, every item in this Section is a NavigationItem, and thus comes with the disclosure indicator on the right as usual. Recreating this is trivial, so it has been left out from the MRE.
I am having trouble recreating the first section, which has that LazyVGrid. I faced 3 problems (as mentioned in the image), of which I have been able to solve the first one only. The other two problems remain. I want to know if this MRE can be fixed, or is my entire approach incorrect.
I am including a minimum reproducible example below.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
RemindersView()
}
}
}
struct RemindersView: View {
private var columns: [GridItem] = [GridItem(.adaptive(minimum: 150))]
private var smartLists: [SmartList] = SmartList.sampleLists
var body: some View {
NavigationView {
List {
Section(header: Text("Using LazyVGrid")) {
grid
}
Section(header: Text("Using HStack")) {
hstack
}
}
.navigationTitle("Store")
}
.preferredColorScheme(.dark)
}
private var grid: some View {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(smartLists) { smartList in
// This use of **ZStack with an EmptyView with opacity 0** is a hack being used to avoid the disclosure indicator on each item in the grid
ZStack(alignment: .leading) {
NavigationLink( destination: SmartListView(list: smartList)) {
EmptyView()
}
.opacity(0)
SmartListView(list: smartList)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
private var hstack: some View {
ScrollView(.horizontal) {
HStack {
ForEach(smartLists) { smartList in
NavigationLink(destination: SmartListView(list: smartList)) {
SmartListView(list: smartList)
}
.buttonStyle(.plain)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
}
struct RemindersView_Previews: PreviewProvider {
static var previews: some View {
RemindersView()
}
}
struct SmartList: Identifiable {
var id: UUID = UUID()
var title: String
var count: Int
var icon: String
var iconColor: Color
static var sampleLists: [SmartList] {
let today = SmartList(title: "Today", count: 5, icon: "20.circle.fill", iconColor: .blue)
let scheduled = SmartList(title: "Scheduled", count: 12, icon: "calendar.circle.fill", iconColor: .red)
let all = SmartList(title: "All", count: 77, icon: "tray.circle.fill", iconColor: .gray)
let flagged = SmartList(title: "Flagged", count: 5, icon: "flag.circle.fill", iconColor: .orange)
return [today, scheduled, all, flagged]
}
}
struct SmartListView: View {
var list: SmartList
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .center) {
Image(systemName: list.icon)
.renderingMode(.original)
.font(.title)
.foregroundColor(list.iconColor)
Spacer()
Text("\(list.count)")
.font(.system(.title, design: .rounded))
.fontWeight(.bold)
.padding(.horizontal, 8)
}
Text(list.title)
.font(.system(.headline, design: .rounded))
.foregroundColor(.secondary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.gray.opacity(0.25))
)
.padding(2)
.frame(minWidth: 150)
}
}
EDIT 1: Adding video demo of what editing the dynamic Grid looks like and how the Grid has dynamic grid items (via the Edit button at the top right): https://imgur.com/a/TV0kifY

SwiftUI: animating scroll on ScrollView programmatically?

I need to programmatically animate the scroll of a scrollview. The scrollview contains either an HStack or a VStack. Code I tested with is this:
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: spacing) {
ForEach(cids, id: \.self) { cid in
....
}
}
.onAppear {
withAnimation(Animation.easeInOut(duration: 4).delay(3)) {
proxy.scrollTo(testCid)
}
}
}
.frame(maxWidth: w, maxHeight: w / 2)
}
The scrollview does land on the item with the testCid, however, it does not animate.
As soon as the view comes on screen the scrollview is already on testCid...
How can I animate the scroll?
The interactive scroll works if you start it from somewhere else (f.e. Button action) but not from the onAppear modifier. I'd guess this is intentional behavior to prevent the user seeing the scrolling when the view appears (or a bug in SwiftUI...). An ugly workaround is to defer the animation with an DispatchQueue.main.async:
import SwiftUI
struct ContentView: View {
let words = ["planet", "kidnap", "harbor", "legislation", "soap", "management", "prejudice", "an", "trunk", "divide", "critic", "area", "affair"]
#State var selectedWord: String?
var body: some View {
ScrollViewReader { proxy in
VStack(alignment: .leading) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
ForEach(words, id: \.self) { word in
Text(word)
.background(self.selectedWord == word ? Color.yellow : nil)
.id(word)
}
}
}
Button("Scroll to random word") {
withAnimation(Animation.easeInOut(duration: 1)) {
let word = words.randomElement()
self.selectedWord = word
proxy.scrollTo(word)
}
}
}
.onAppear {
DispatchQueue.main.async { // <--- workaround
withAnimation(Animation.easeInOut(duration: 1).delay(1)) {
let word = self.words.last
self.selectedWord = word
proxy.scrollTo(word)
}
}
}
}
.padding(10)
}
}

SwiftUI Add view below a list

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())
}

SwiftUI List - How to scroll to item

I have list with items. How can i scroll to list 12. I can use geometry reader to calculate offset. But how to scroll to this offset?
List {
ForEach(0..<12) { index in
Text("...")
}
}
Form Xcode 12, You can turn in to a ScrollView and then you can do it by .scrollTo(id):
var body: some View {
ScrollViewReader { scrollProxy in
ScrollView {
ForEach((1...100), id: \.self) { Text("\($0)") }
}
Button("Go!") {
withAnimation { scrollProxy.scrollTo(50) }
}
}
}
Note that ScrollViewReader should support all scrollable content, but now it only supports ScrollView