SwiftUI view in list, detect when fully visible? - ios

I have this simple view:
struct ContentView: View {
let items = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
var body: some View {
List {
ForEach(items, id:\.self) { i in
Text("item \(i)")
.frame(height:200)
.onAppear {
print("A: \(i)")
}
.onDisappear {
print("D: \(i)")
}
}
}
}
}
I would like to know when the cell's rect is fully visible. As is, onAppear triggers as soon as the cell comes in the view as I scroll. However, I'd like to get a hook that tells me when the cell is fully visible.
How could I do that in SwiftUI?

Related

SwiftUI - How to handle drop event outside of my View

I'm implementing drag & drop in LazyVGrid. I'd like to call some function when drop happens outside of my View. How can I get the callback?
Here is my code:
var body: some View {
LazyVGrid(columns: ...) {
ForEach(...) { item in
createItem(...)
.onDrop(of: [.text], delegate: DropRelocateDelegate(didFinishDragging: {
draggingItem = nil
viewModel.didFinishDragging()
}))
}
}
}
As you can see I'm making some changes when drop happens. However this is only called when drop happens inside a grid. I'd like to make the exact same changes when drop happens outside of the View.
The most simple way would be to nest your LazyVGrid in a ZStack with a Rectangle(). The Rectangle() should fill the screen. You can then set the ZStack item alignment to top or whatever else you like.
struct SwiftUIView: View {
var body: some View {
ZStack(alignment: .top) {
Rectangle().foregroundColor(.clear)
LazyVGrid(columns: ...) {
ForEach(...) { item in
createItem(...)
.onDrop(of: [.text], delegate: DropRelocateDelegate(didFinishDragging: {
draggingItem = nil
viewModel.didFinishDragging()
}))
}
}
}
.onDrop(of: [.text], delegate: DropRelocateDelegate(didFinishDragging: {
draggingItem = nil
viewModel.didFinishDragging()
}))
}
}
Now in some cases, there may not be a need for putting onDrop() on the LazyVGrid items. Adding it to the ZStack should affect the entire screen. However, I included it just in case.

Lag in cell unhighlighting if I add a context menu button to it

I noticed that when I add a context menu button to the cell, this causes a lag in unhighlighting the cell when pushing and popping back another view to the navigation stack.
struct ContentView: View {
var body: some View {
NavigationView {
List(1..<5, id: \.self) { number in
NavigationLink(destination: Text("Hello, world!")) {
Text("\(number)")
.contextMenu { Button("") {} } // this line causes the lag.
}
}
}
}
}

Why background color of List is different while presenting view in SwiftUI?

I am implementing List in Presented view (AddItemView). I want background color same as List in any view.
struct HomeView: View {
#State private var showAddItemView: Bool = false
var body: some View {
NavigationView {
List(0..<9, id: \.self) { i in
Text("Row \(i)")
}
.navigationTitle("Home")
.navigationBarItems(trailing:
Button("Add") {
showAddItemView.toggle()
})
.sheet(isPresented: $showAddItemView) {
AddItemView()
}
}
}
}
struct AddItemView: View {
init(){
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
NavigationView {
List(0..<9, id: \.self) { i in
Text("Row \(i)")
}.background(Color(UIColor.systemGroupedBackground))
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Add Item View", displayMode: .inline)
}
}
}
Above code is creating simple List with InsetGroupedListStyle. But background colour is different while Presenting view (AddItemView in my case).
I have already tried https://stackoverflow.com/a/58427518/7084910
How to set background color of List in presented view as in any normal list. Red/Yellow/Green can set to List, "BUT" I want same as normal list in HomeView that will work in light & dark mode.
Use this:
var body: some View {
NavigationView {
List(0..<9, id: \.self) { i in
Text("Row \(i)")
}
.colorMultiply(Color.red)
}
}
They think it is better visual representation for .sheet (probably to make it more determinable)...
SwiftUI 2.0
The .fullScreenCover gives what you want. Alternate is to present AddItemView manually using some transition.
.navigationBarItems(trailing:
Button("Add") {
showAddItemView.toggle()
})
.fullScreenCover(isPresented: $showAddItemView) {
AddItemView()
}

SwiftUI NavigationBar not disappearing while scrolling

I want to hide my NavigationBar while scrolling, actually It must hide automatically but when I tried with multiple views It doesn't work. Also, It works when I remove custom views and capsulate List with NavigationView. But I need SearchBar and StatusView view. Is there any suggestion?
By the way, I run it on the device, I use canvas here for demonstration purposes.
Thank you.
var body: some View {
NavigationView {
VStack(spacing: 0) {
SearchBar(searchText: $viewModel.searchText)
StatusView(status: $viewModel.status)
Divider()
List(0...viewModel.characters.results.count, id: \.self) { index in
if index == self.viewModel.characters.results.count {
LastCell(vm: self.viewModel)
} else {
ZStack {
NavigationLink(destination: DetailView(detail: self.viewModel.characters.results[index])) {
EmptyView()
}.hidden()
CharacterCell(character: self.viewModel.characters.results[index])
}
}
}
.navigationBarTitle("Characters", displayMode: .large)
}
}
.onAppear {
self.viewModel.getCharacters()
}
}
Just idea, scratchy... try to put your custom views inside List as below (I know it will work, but I'm not sure if autohiding will work)
NavigationView {
List {
SearchBar(searchText: $viewModel.searchText)
StatusView(status: $viewModel.status)
Divider()
ForEach (0...viewModel.characters.results.count, id: \.self) { index in
...
Based on Asperi's solution, I wanted to have the SearchBar and StatusView always visible, i.e. it should stop scrolling after the title has disappeard. You can achieve this with a section header like shown below (just a rough sketch):
NavigationView {
List {
Section(header: {
VStack {
SearchBar...
StatusView....
}
}) {
ForEach...
}
}
}

How to display a text message at the Center of the view when the List datasource is empty in SwiftUI?

struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarkData) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
}
I want to display a message at the centre of the view when the list view (table view) is empty. What is the best way to achieve this in SwiftUI. Is checking for the data source count in "onAppear" and setting some sort of Bool value the correct approach ?
struct LandmarkList: View {
var body: some View {
NavigationView {
if landmarkData.count == 0 {
VStack {
Text("is empty")
} else {
List(landmarkData) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
}
}
I would simply try an if else statement. If landmarkdata is nil or count = 0, show a text. Else show the list.
If you want your message to have the same behavior as a list item, use ScrollView and GeometryReader.
Embed the condition state in the Group, so that the NavigationView is displayed in any case.
Don't forget to change the background color and ignore the safe area, for more resemblance to a list.
struct LandmarkList: View {
var body: some View {
NavigationView {
Group {
if landmarkData.isEmpty {
GeometryReader { geometry in
ScrollView {
Text("Your message!")
.multilineTextAlignment(.center)
.padding()
.position(x: geometry.size.width/2, y: geometry.size.height/2)
}
}
.background(Color(UIColor.systemGroupedBackground))
} else {
List(landmarkData) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
.ignoresSafeArea()
.navigationTitle("Title")
}
}
}
You can also use #ViewBuilder to provide an empty state view in the list, as suggested in this answer.

Resources