I have a LazyVGrid in a ScrollViewcontaining up to 1000s of items. I started with having a button as item:
ScrollView {
LazyVGrid(columns: columns, spacing: 0) {
ForEach(0..<number, id: \.self) { i in
Button(action: {
self.action(i)
}) {
ZStack {
...
}
}
}
}
}
But that leads to extremly high cpu usage when scrolling (always 97%+) and very "unsmooth" scrolling. When I change it to Text with inTapGesture it is way more performant and cpu only goes up to >90% for a short time.
ScrollView {
LazyVGrid(columns: columns, spacing: 0) {
ForEach(0..<number, id: \.self) { i in
ZStack {
Text().onTapGesture {
self.action(i)
}
...
}
}
}
}
}
Why is that the case? Is it so much harder to render a Button than a Text? I like the "touch" gesture of the button that's why I would prefer a Button, but not if it is so much slower.
Normally there is always a competition between drag gesture (means items use drag gesture like: Button, Slider, Toggle, . .) and scrolling functionality (means items you can scroll: ScrollView, List, Picker, . .) and it is better always use Text with on tap gesture because it doesn't compete with scrolling on drag gesture.
Button use drag gesture but Text does not, there you would see better Performance with Text.
Related
I've build a ScrollView which contains 0-3 images and a multiline text field in a VStack. I also added a ScrollViewReader inside the scrollview and use it to scroll to the bottom of the text field upon certain events (user starts typing, image collection changes).
The point is: sometimes it works, sometimes it doesn't. When it does not work I realized, that when I scroll a little bit by hand and then try again (e.g. typing) it works.
Not sure if this is relevant, but ImageOrPlaceholderComponent first shows a placeholder as long as the image within currentEntryImages is nil, and the image after that (both states imply a change to currentEntryImages and should thus result in scrolling to the bottom of the text field).
NavigationStack {
ScrollView {
ScrollViewReader { scrollview in
VStack {
// Attached images.
AnyLayout(VStackLayout(spacing: 2.5)) {
ForEach(values: currentEntryImages) { entryImage in
ImageOrPlaceholderComponent(image: entryImage)
.clipped()
}
}
// Text field for the entry with toolbar.
TextField("...", text: $entryDTO.text, axis: .vertical)
.id(entryTextFieldAnchor)
.multilineTextAlignment(.leading)
.padding()
.focused($mainTextFieldFocused)
.onAppear { mainTextFieldFocused = true }
// Scroll to the bottom of the text field, when the user is typing ...
.onChange(of: entryDTO.text) { _ in
withAnimation {
scrollview.scrollTo(entryTextFieldAnchor, anchor: .bottom)
}
}
// ... or the entry images have changed.
.onChange(of: currentEntryImages) { _ in
withAnimation {
scrollview.scrollTo(entryTextFieldAnchor, anchor: .bottom)
}
}
}
}
}
}
For learning purposes I am trying to make a TVOS 16.1 app. I load data in via an API.
The problem that I cannot solve is as follows;
When images are loaded in and presented in a LazyVGrid with 4 columns, and I am at the bottom of the list - when I scroll up the LazyVGrid while pressing and keep holding the 'up' button on my remote control, LazyVGrid is losing focus and the menu bar at the top will receive the focus. It only scrolls up a line or 4 or so and then the focus is gone.
the TVShowCard is a view that will create buttons which will hold the image of the tvshow.
ScrollView(.vertical) {
LazyVGrid(columns: [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
], spacing: 20) {
ForEach(top_Rated_TVShows, id: \.self) { tvshow in
TVShowCard(tvshow: tvshow)
}
}
}
When I do exactly the same using LazyVStack as follows, it works good. I can press & hold the up or down button and it will scroll through the entire list :
ScrollView(.vertical) {
LazyVStack {
Grid {
ForEach(top_Rated_TVShows.indices, id: \.self) { index in
if index % 4 == 0 {
GridRow {
ForEach(0..<4, id: \.self) { subIndex in
if index + subIndex < top_Rated_TVShows.count {
TVShowCard(tvshow: top_Rated_TVShows[index + subIndex])
.frame(maxWidth: 400)
} else {
Image(systemName: "placeholder")
.frame(maxWidth: 400)
}
}
}
}
}
}
}
}
But it seems to me that the performance of LazyVGrid is much better, the code is also easier to understand and read. So I prefer LazyVGrid, but bcs of this scrolling issue it is unusable.
Does anyone know what might cause this problem and how to solve it ?
I am trying to use LazyVGrid in TVOS 16.1, but when I am at the bottom of the list and scroll up. After a few scrolls it is losing focus, and the focus will go to the menu bar at the top of the screen.
I have a grid view with a floating button overlaid on top, which appears and disappears through the bottom edge (using .transition(.move(edge: .bottom)). However, when it disappears, a.k.a when it slides back down, it freezes for a second, and then goes all the way down. I've been trying to pinpoint the culprit, but I haven't found anything – it's not because I'm using an overlay, nor is it because of the safe area, and it's not due to the button's shape.
Here's a screen recording showing the button appearing and disappearing, and the momentary freezing.
And this is what my code looks like:
var body: some View {
ScrollView {
// ...
}
.overlay(alignment: .bottom) {
VStack {
if condition {
Button(action: { /* ... */ }) {
Text("Share")
}
.background { LinearGradient(...) }
.clipShape(Capsule())
.transition(.move(edge: .bottom))
}
}
.animation(.linear, value: condition)
}
}
Any idea why the transition momentarily freezes like this?
I want to change the discoverability title of a keyboard shortcut in SwiftUI.
As you can see below the title shows in the popup if used in text button, but if you use an image for the button it doesn't show in the popup (when holding cmd on the keyboard to view supported shortcuts by the app).
struct ContentView: View {
var body: some View {
VStack {
Button("Save to Favorites") {
}
.keyboardShortcut("a")
Button {
} label: {
Image(systemName: "heart.fill")
}
.keyboardShortcut("s")
}
}
}
How can I add a title to the shortcuts help popup?
Note that I have tried all accessibility stuff, i.e. label, identifier, hint, etc... and It didn't work.
Not a super elegant solution but I got it working by adding a Text with a .frame size of width: 0, height: 0. This effectively hides the Text from view but ensures it appears when the user holds down the ⌘ key.
Consider putting it in a ZStack too as the default arrangement could have it ever so slightly off centre.
VStack {
Button("Save to Favorites") {
}
.keyboardShortcut("a")
Button {
} label: {
ZStack {
Text("heart")
.frame(width: 0, height: 0) // <- this part
Image(systemName: "heart.fill")
}
}
.keyboardShortcut("s")
}
I am trying to use the whole iPhone area for my app.
I have this HStack at the top, used to create a custom toolbar.
var body: some View {
VStack (spacing:0) {
MyTopbar()
// other controls
Spacer()
}
.edgesIgnoringSafeArea(.top)
This appears like this on new devices with a notch and old devices without a notch. The notch cuts my menu.
I can solve that by adding a spacer with a frame height before MyTopbar() on the vertical stack but first of all this seems to be a very awful solution. First I have to guess a height for that spacer. Then I have to detect if the device has a notch or not (?).
Is there a better way?
You can think of it as layers (content that respects safe area and content that doesn't).
Something like this perhaps:
struct ContentView: View {
var body: some View {
ZStack {
Color.blue.ignoresSafeArea() // Whatever view fills the whole screen
VStack (spacing:0) {
MyTopbar()
// other controls
Spacer()
}
}
}
}
A possible solution to add clear color with safe area height. No need for much calculation.
var body: some View {
VStack (spacing:0) {
Color.clear.frame(height: Color.clear.frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
MyTopbar()
// other controls
Spacer()
}
.edgesIgnoringSafeArea(.top)