I use a TabView to simulate a horizontal scrolling paged list. This work fine. But I want to change the background, according to the currently active tab. I utilise .onAppear() for this, but this results in the images not being in sync with the item in the current tab (see video: https://youtube.com/shorts/aQxrErRmCi8?feature=share)
Basically, it should only change the background whenever the tab is 'fully' selected and centered on screen. Not as soon as the next or previous tab comes onto screen just a few pixels.
Secondly, I want to be able to set the selected programmatically, for instance after I insert a new object, I want to select the first tab. How Can I achieve that. The tabs have the id of my item (game) as tag.
Relevant code:
#State private var backgroundImage = AppImages.gameDefaultBackgroundImage
#ViewBuilder var tabList: some View {
TabView {
ForEach(gamesViewModel.games) { game in
GameCardView(game: game)
.tag(game.id)
.onAppear {
withAnimation(.easeInOut) {
backgroundImage = gamesViewModel.getGameImageCoreData(for: game)
}
}
.listRowBackground(Color.clear)
.frame(width: getScreenRectangle().width - 20)
.onTapGesture { showAnnotationsView(for: game) }
.onLongPressGesture(minimumDuration: 0.15) { gameDetailsView(for: game) }
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
If more code is needed, please ask ...
Related
To my knowledge, the only way to assign a navigation bar a background color that is separate from the rest of the screen, you set the background color to whatever object you have flush with the navigation view. (In this case, its a divider with the background set to red)
My intention is to then place a view flush to the divider in an attempt to create some sort of "subtitle view". The problem is, as you can see, there is a space between my Stack and my Divider... I'm not sure what is causing this space and I am not sure how to get rid of it.
My first thought was, perhaps there is some safe area being adhered to... That said, I tried ignoring that, but that didn't work.
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
Divider()
.background(.red)
.navigationTitle("Main Title")
// There is a space here
ZStack {
Rectangle().frame(height: 35)
Text("Subtitle View")
.foregroundColor(.white)
}.edgesIgnoringSafeArea([.top, .bottom])
Spacer()
}.background(.blue)
}
}
}
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.
In several places in my app, I implement a paged loading scheme using a ScrollView and a LazyVStack, with an ProgressView after the loaded items. The ProgressView is only shown if the model has more items to load, and on appear of the ProgressView, loading of the next page is triggered.
The new items get appended to the list in the model, and they get rendered, pushing the ProgressView off the bottom of the screen. When the user scrolls up, the ProgressView reappears, and triggers loading of the next page.
For some reason, in one list where I’m using a LazyVGrid inside a LazyVStack, when the next page loads, the entire view scrolls such that the bottom of the stack is still displayed, causing the ProgressView to sit there spinning. It’s as if the ScrollView is pinning the scroll to the bottom of its content, and I can’t figure out why.
The view looks like this:
ScrollView
{
LazyVStack
{
LazyVGrid(columns: self.columns, spacing: 20.0)
{
if let products = self.collection.products,
products.count > 0
{
ForEach(products)
{ inProduct in
NavigationLink(destination: ProductDetailsContainerView(searchResult: WNProductSearchResult(product: inProduct)))
{
ProductListingCell(imageURL: inProduct.imageURL,
title: inProduct.name,
listingCount: inProduct.availableCount,
price: inProduct.marketPrice)
}
}
}
}
if let products = self.collection.products,
products.count == 0
{
self.emptyDisplay
}
// Progress loader triggers loading…
if self.collection.hasMore
{
HStack
{
Spacer()
ProgressView()
Spacer()
}
.padding(.top, 40)
.onAppear { self.collection.load(next: 20) }
}
}
.padding(20)
}
Even more bafflingly, this only occurs after the second page loads. The first page loads as expected; then you scroll to the bottom, the ProgressView is revealed, the second page loads, and the items fill in without moving the scroll position. If you then scroll up again, the ProgressView is revealed, the third page loads, and the items fill in, but the scroll view is repositioned such that you’re looking at the new bottom of the list.
Hopefully you can see the behavior:
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)
I don't understand how to delete or move a item on macOS. What action should I need to do with the mouse to trigger onDelete or onMove events?
#State var wishList = ["Item 1", "Item 2", "Item3"]
var body: some View {
List {
ForEach(wishList, id:\.self) { item in
Button(action: {
}) {
Text(item)
}
}
.onDelete { offsets in
}
.onMove { source, target in
}
}
}
Move:
Click and drag rows.
Delete:
Swipe with (two fingers on trackpad or one finger on magic mouse), like the way you scroll horizontally.
Note that you should NOT click and drag the row like the way you swipe in iOS simulator. Just a simple mac horizontal scroll is enough.
These gestures won't work if you have an ordinary two-button mouse. You will need to make affordances in your UI for people with simpler mice.