SwiftUI: Update height of an existing row in list - swiftui

I'm new to programming in SwiftUI. I created a list of a custom view that expands on tap. However, whenever I tap the custom view, the height of the list row doesn't update. Here's what my code looks like:
List(){
ForEach(ingredients){ ingredient in
IngredientList(ingredient: ingredient)
}
}
struct IngredientList: View {
let ingredient: Ingredient
#State private var showBody = true
var body: some View {
Hstack{
VStack(alignment: .leading){
Text("Text 1")
if(showBody){
Divider()
Text("Text 2")
.font(.caption)
}
}
}
.contentShape(Rectangle())
.onTapGesture {
showBody.toggle()
}
}
}
I think it has something to do with state of the list but I can't seem to figure it out.

I found an ugly but working solution. On the expanded item change, check whether it is a selection mode, remember the current data set, clear it, and set it back with DispatchQueue.main.async. It works on iOS 15.4.1. I hope Apple will fix the issue in the subsequent releases.
Update: try introspect. You will be able to retrieve the underlying table view and call UITableView.reloadData() on the expanded row change

Related

gutters on sides of List in WatchOS

I'm dipping my toe into SwiftUI and WatchOS for the first time. I'm making good progress, but I can't figure out how to get rid of the black "gutters" on either side of my Image controls. I've tried setting all the backgrounds to white, but the gutter persists.
What property on which view do I need to set to change the color of the gutters to match the background?
SwiftUI
struct ContentView: View {
var body: some View {
List {
Image("cat-1").resizable().scaledToFill().background(Color.white)
Image("cat-2").resizable().scaledToFit().padding(5).background(Color.white)
Image("cat-3").resizable().scaledToFit().padding(.top, 5).background(Color.white)
}.background(Color.white).listStyle(CarouselListStyle())
.background(Color.white)
}
}
Try adding a
.listRowPlatterColor(.clear)
put it inside the list like this...
struct ContentView: View {
var body: some View {
List {
Image("cat-1")
.resizable()
.scaledToFit()
.listRowPlatterColor(.clear)
Image("cat-2").resizable().scaledToFit().padding(5).background(Color.white)
Image("cat-1")
.resizable()
.scaledToFit()
.padding(.top, 5)
.background(Color.white)
}
.listStyle(CarouselListStyle())
}
}
I put it in the first item and left it off of the second and third so that you could see the difference. This other question can provide some more details:
How to style rows in SwiftUI List on WatchOS? .
You should then be able to style it however you like.

Swiftui: How to Close one Tab in a TabView?

I'm learning SwiftUI and having trouble with closing Each Tab View Element.
My App shows photos from user's album by TabView with pageViewStyle one by one.
And What I want to make is user can click save button in each view, and when button is clicked, save that photo and close only that view while other photos are still displayed. So unless all photos are saved or discarded, if user clicks save button, TabView should automatically move to another one.
However, I don't know how to close only one Tab Element. I've tried to use dismiss() and dynamically changing vm.images element. Latter one actually works, but it displays awkward movement and it also requires quite messy code. How could I solve this issue?
Here is my code.
TabView {
ForEach(vm.images, id: \.self) { image in
TestView(image: image)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
struct TestView: View {
#ObservedObject var vm: TestviewModel
...
var body: some View {
VStack(spacing: 10) {
Image(...)
Spacer()
Button {
...
} label: {
Text("Save")
}
}
You need actually to remove saved image from the viewModel container, and UI will be updated automatically
literally
Button {
vm.images.removeAll { $0.id == image.id } // << here !!
} label: {
Text("Save")
}
You need to use the selection initializer of TabView in order to control what it displays. So replace TabView with:
TabView(selection: $selection)
Than add a new property: #State var selection: YourIdType = someDefaultValue, and in the Button action you set selection to whatever you want to display.
Also add .tag(TheIdTheViewWillUse) remember that whatever Id you use must be the same as your selection variable. I recommend you use Int for the simple use.

How to do Apple Music-like navigation in SwiftUI? Custom List and NavigationView has highlight not going away

This is my example that I am trying to get to work:
struct ContentView: View {
let links = ["Item 1", "Item 2", "Item 3", "Item 4"]
var body: some View {
NavigationView {
ScrollView {
Text("My Title")
List(links, id: \.self) {
link in
NavigationLink(destination: TestView()) {
Text(link)
.padding(.vertical, 4)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.frame(height: 178)
Text("Some more content here")
}
}
}
}
Note: TestView is just some view with the text hello world on it.
I am trying to copy Apple Music's style of navigation. I tried putting a Button in the NavigationLink but tapping it on the text wouldn't change views, and I couldn't find a way to reliably change the color of the row when tapped, at the same time. Also in some approach, I managed to make it work, but the way the colors animate is different, i.e. it fades from A to B, over ~100ms whereas what I'm trying to achieve is to animate between the states instantly (like in Apple Music).
My current approach is using a List, putting NavigationLinks inside it and then cutting off the whole view by giving it a height. This way I can put it alongside other content.
It's working fine for now, but whenever I click on an row and go back, the row is still highlighted, when it shouldn't. Is there a way to make it so that it deselects when going back to the screen somehow?
I think this bug is being caused by the List being inside a ScrollView, since when I removed ScrollView, the list worked properly, and there wasn't this highlight bug. But I need to be able to put my content with the list, and I don't intend to have a list take up the whole screen.
Is there any way to fix this bug with this approach? I'm also willing for other ways to achieve the same result without using List.
Trying to use ForEach instead ofList?
With a view for row (CustomRow) where you can pass link item and set custom dividing line, background etc ...
ForEach(links, id: \.self) { link in
NavigationLink(destination: TestView()) {
CustomRow(item: link)
}
}
.frame(height: 178)

generic param Label cannot be inferred in SwiftUI

enter image description hereI am new to SwiftUI. When I embed a toggle switch inside Vstack, it says "generic param Label cannot be inferred" in SwiftUI. May I know the cause for this error and how to fix it.Thank you
Looks like you might have missed specifying the label or have an error there. I have created the below code that has a toggle button inside VStack.
import SwiftUI
struct ContentView: View {
#State private var isOn: Bool = false
var body: some View {
VStack (alignment: .leading, spacing: 10){
Toggle(isOn: $isOn){
Text("Toggle me to set values")
}
Text("Toggle Value: \(isOn.description)")
Spacer()
}.padding()
}
}
Let me know if this helps.
Toggle Screenshot
I have embedded a text, Two toggles and stepper and a button in Vstack and here I have missed to provide action to that button, which resulted in the issue "generic param Label cannot be inferred".previous Code with issue. After adding the button action

SwiftUI how to detect when a view has the focus?

I have two List's in a view and want to be able to determine which list currently has the focus in order to show the correct details of the selected item in the list in a details panel.
The following code never seems to get called, can anyone indicate whether there is another correct way to determine when focus changes.
struct StoreList: View {
#EnvironmentObject private var database: Database
#Binding var selectedStore: Store?
var body: some View {
List(selection: $selectedStore) {
ForEach(database.stores, id: \.self) { store in
StoreRow(store: store).tag(store)
.focusable(true, onFocusChange: { isFocused in
print("focus changed")
if isFocused {
self.database.selectedType = .store
}
})
}
}
.focusable(true, onFocusChange: { isFocused in
print("focus changed")
if isFocused {
self.database.selectedType = .store
}
})
}
}
In the meantime I will explore detecting mouse clicks on the Rows since the user would need to click on an item in the list to move the focus.
Currently I am setting the selectedType value when an item changes (i.e. $selectedStore) in the view model (database) but if the user selected the already selected item in the other list then the value does not get updated but the List and list item does get the focus - well the visual colour change indicates it has the focus.
EDIT:
I have also tried processing the onTapGesture callback which works fine except it replaces the List rows default behaviour. How can I make sure the event is passed through to the List as this might work then.
The easiest method of reacting to focus is
struct DummyView: View {
#Environment(\.isFocused) var isFocused
var body: some View {
Text("My view")
.padding()
.background(isFocused ? Color.orange : Color.black)
}
}