SwiftUI: List data dissapears after you dismiss sheet - ios

I have a list of horizontal images. And a little profile/settings button in the navigation bar. When you click the profile button, a sheet view slides up, and when you dismiss it, the list seems to lose it's cells (HStacks of other images basically)
Here's the code that displays the sheet and list
var body: some View {
NavigationView {
List {
ForEach(0..<self.categories.count) {
ExploreRow(title: self.categories[$0])
}
}
.navigationBarTitle("Justview")
.navigationBarItems(trailing: profileButton)
.sheet(isPresented: self.$showingProfile) {
Text("User Profile: \(self.userData.userProfile.username)")
}
}
}
ExploreRow is essentially a horizontal stack in a scroll view that displays images loaded from the web.
Before Sheet image:
After Sheet dismiss image:

I assume there are not many categories in the List (as they should not be many), so the simplest is just force-rebuild list (supposing you don't make anything heavy in ExploreRow.init, like
List {
ForEach(0..<self.categories.count) {
ExploreRow(title: self.categories[$0])
}.id(UUID())
}
.navigationBarTitle("Justview")
*The issue is due to List's caching-reusing row views, it is known.

Related

Dismiss a SwiftUI navigation view on an iPad in portrait mode when a selection is made

On my SwiftUI app, I have a navigation view on the left side on an iPad in portrait mode, and after I make a selection, I want the navigation view to disappear. It does this for the first time, but subsequent times, I have to click on the view on the right for it to disappear. I have no idea how to make this happen consistently.
Here is a very simple chunk of code that demonstrates the problem. When you run it and click "< Back", it will show the menu of options on the left, and when you first click an option, it disappears. The next time you click the "< Back" at the top left and then click another selection, it will continue to show that list until you click the pane on the right. It does update the pane on the right with your choice each time, as it should.
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("You selected option 1")) {
Text("Option 1")
}
NavigationLink(destination: Text("You selected option 2")) {
Text("Option 2")
}
NavigationLink(destination: Text("You selected option 3")) {
Text("Option 3")
}
}
}
}
}
Thanks for your help!

How to check if item is visible - SwiftUI ScrollView

Trying to programmatically determine when an item is being displayed on screen in a ScrollView in SwiftUI. I understand that a ScrollView renders at one time rather than rendering as items appear (like in List), but I am constrained to using ScrollView as I have .scrollTo actions.
I also understand that in UIKit with UIScrollView, it is possible to use a CGRectIntersectsRect between the item frame and the ScrollView frame in the UIScrollViewDelegate but I would prefer to find a solution in SwiftUI if possible.
Example code looks like this:
ScrollView {
ScrollViewReader { action in
ZStack {
VStack {
ForEach(//array of chats) { chat in
//chat display bubble
.onAppear(perform: {chatsOnScreen.append(chat)})
}.onReceive(interactionHandler.$activeChat, perform: { _ in
//scroll to active chat
})
}
}
}
}
Ideally, when a user scrolls, it would check which items are on screen and zoom the view to fit the largest item on screen.
When you use VStack in ScrollView all content is created at once at build time, so onAppear does not fit your intention, instead you should use LazyVStack, which will create each subview only before it appears on screen, so onAppear will be called when you expect it.
So it should be like
ScrollViewReader { action in
ScrollView {
LazyVStack { // << this !!
ForEach(//array of chats) { chat in
//chat display bubble
.onAppear(perform: {chatsOnScreen.append(chat)})
}.onReceive(interactionHandler.$activeChat, perform: { _ in
//scroll to active chat
})
}
}
}

How to Programmatically Navigate List by Row Content?

I have a List within a NavigationView where each view under List should have navigatable elements attached to it (cover image, user avatar + name, etc.) For example, clicking the cover image navigates to view A, while clicking the user's name/avatar navigates to view B. Sadly, in all cases, the entire list element was clickable and did not grant the intended behavior.
At first, I tried wrapping my content within a NavigationLink.
NavigationLink(destination: Text("Media"), tag: .media, selection: $selection) {
WebImage(url: URL(string: activity.media?.coverImage?.extraLarge ?? ""))
.resizable()
.placeholder { color }
.cornerRadius(8)
.frame(width: 90, height: 135)
}
This causes an arrow to appear to indicate the view is navigatable for the user but is unwanted in this situation. It was also taking up a lot of space from the view unnecessarily.
My next attempt was to wrap the view and NavigationLink in a ZStack.
ZStack {
NavigationLink(destination: Text("Media"), tag: .media, selection: $selection) {
EmptyView()
}.hidden()
WebImage(url: URL(string: activity.media?.coverImage?.extraLarge ?? ""))
.resizable()
.placeholder { color }
.cornerRadius(8)
}.frame(width: 90, height: 135)
The .hidden() modifier was applied to the NavigationLink to prevent the arrow from appearing when the image was transparent. While this solution both hides the arrow and cleans up the extra space, there are two issues:
The entire list element is still clickable.
A ZStack covered by the .frame modifier requires I know how large I want to make it. The user's name & avatar view can't easily overcome this dilemma.
Thirdly, I tried wrapping the view in a Button where the label was the cover image and the action was to change selection to navigate programmatically, but this brought the spacing issue from #1 and the overall issue of the list element being clickable.
I later discovered a solution that would cut down the previous issues I had, but brought one problem. To understand it, this is what my main activity view looks like:
NavigationView {
List(viewModel.activities) { activity in
ActivitySelectionView(activity: activity, selection: $selection)
}.navigationTitle("Activity Feed")
}.onAppear {
viewModel.fetchActivities()
}
By encapsulating List(...) {...} in a ScrollView and changing List to a ForEach, I was able to produce the output I wanted: clickable view within an element, the cover image became lighter when clicking on it, opposed to the list element becoming darker as a whole until let go, etc.
However, this is not a list. It does not look good, nor will it look better on other platforms (this is an iOS project). For example, this code does not respect the edges as a list does. It also does not include a divider, but the Divider struct can help. I feel this is not the right solution to this problem.
To sum it all up, how do I create a List inside a NavigationView where the list respects what views inside an element are navigatable?
I found an elegant solution to my problem, so I'd like to share it for people who may stumble upon this question in the future.
You need to use a ScrollView within the List {...} somewhere. In the ScrollView block, it's perfectly suitable to make certain elements in the list cell navigatable.
NavigationView {
List(1..<11) { num in
ScrollView {
Text("\(num)!")
NavigationLink(destination: Text("Number: \(num)")) {
Text("Click me")
}
}
}
}

SwiftUI - Button action draws on an image

Hobbyist ios coder here...
I have an old Objective C project I am trying to recreate in swiftui.
When I click a button, it draws a CGRect and fills in the peramiters of image source from the IBAction I wrote out that i can then drag arround the screen.
Old way I used to do it
How do i do this in SwiftUI?
My empty SwiftUI Button Code
I currently have an image drawn within a VStack that I can move arround and resize, but I want it to not exist on app load, but for it and others to be drawn on user request. IE button press.
I have no idea what it is im supposed to search for to find this answer as searching for button and image instantly gives me tutorials on how to add images to a button, not a button that draws a fresh interactable element on the view.
Thank you to anyone who can shed more light on this.
EDIT - On pressing the button a second time I need the image to spawn again so there would be multiple instances of it.
I'm not 100% sure if I understand this correctly, but it's simply a matter of displaying an image when a button is clicked?
You already said that you got an image with the desired behavior, but you don't want it to be there from the beginning. I think you are looking for an alternative in SwiftUI for addSubview, but there is no direct one.
What you might be looking for is #State in SwiftUI. Changing a #State variable will force your view to redraw itself. (There are more then just #State variables that to this)
You can draw your Image based on a condition. On your button click you could change the state variable that your condition is based on to redraw your view and to display your Image. I use the toggle() function in my example, so clicking the Button a second time will result in removing the image.
import SwiftUI
struct ContentView: View {
#State private var showImage = false
var body: some View {
VStack {
if showImage {
Image(systemName: "gear") // Your working image
}
Button(action: {
self.showImage.toggle()
}, label: {
Text("draw image")
})
}
}
}
Here is an example how you could add more and more Images when clicking the button again and again. I changed the Type of the #State variable to an array and then iterate over that array via a ForEach.
import SwiftUI
struct ContentView: View {
#State private var images: [UUID] = []
var body: some View {
VStack {
ForEach(images, id: \.self) { _ in
Image(systemName: "gear") // Your working image
}
Button(action: {
self.images.append(UUID())
}, label: {
Text("draw image")
})
}
}
}

How to create a ContextMenu animation like in the native iOS Files app?

I have a CollectionView with album cells looking like the ones in the photos app. I want to apply a ContextMenu to those cells. But by doing so the ContextMenu animation is applied onto the whole cell which doesn't look nice. What I want to achieve is the animation only to affect the UIImageView inside the cell without the labels below the image.
This is how it looks right now during the animation. You can clearly see the shadow going around the labels at the bottom.
This is how it looks like in the Files app. That's what I want to achieve. The animation is only applied to the folder image, not the labels below.
Make sure to add the content menu on the image and not on the Stack (or whatever you use to group the items).
The following code produces the following example:
struct ContentView: View {
var body: some View {
VStack{
Image("swift").resizable().frame(width: 200, height: 200).border(Color.black).contextMenu {
Text("Menu Item 1")
Text("Menu Item 2")
Text("Menu Item 3")
}
Text("Label").font(.title)
}
}
}

Resources