admin管理员组文章数量:1356884
I have a simple SwiftData app. Whenever ImageLibraryView
appeared, there were microhangs because images inside each ThumbnailView
were being loaded synchronously.
So I solved that by loading the images asyncronously. I animated their appearence so they fade in once loaded.
This worked great, however an undesired effect is every new ThumbnailView
that appears when scrolling fades in. This quickly becomes annoying to see. Even when scrolling back up, the thumbnails that had previously appeared earlier animate back in.
How do I control the animation so that only the first several ThumbnailViews
(or however many that fit the screen) animate in? Then once they're done, animation is disabled for all.
Thank you!
struct ImageLibraryView: View {
@Query var images: [Item] = []
var body: some View {
ScrollView {
ForEach(images) { item in
ThumbnailView(item: item)
}
}
}
}
struct ThumbnailView: View {
var item: Item
@State var uiImage: UIImage?
var body: some View {
Rectangle().fill(.gray)
.aspectRatio(1.0, contentMode: .fit)
.overlay {
if let uiImage {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
}
}
.animation(.easeInOut, value: uiImage)
.task {
uiImage = await loadThumbnailFromDisk(for: item.id)
}
}
}
I have a simple SwiftData app. Whenever ImageLibraryView
appeared, there were microhangs because images inside each ThumbnailView
were being loaded synchronously.
So I solved that by loading the images asyncronously. I animated their appearence so they fade in once loaded.
This worked great, however an undesired effect is every new ThumbnailView
that appears when scrolling fades in. This quickly becomes annoying to see. Even when scrolling back up, the thumbnails that had previously appeared earlier animate back in.
How do I control the animation so that only the first several ThumbnailViews
(or however many that fit the screen) animate in? Then once they're done, animation is disabled for all.
Thank you!
struct ImageLibraryView: View {
@Query var images: [Item] = []
var body: some View {
ScrollView {
ForEach(images) { item in
ThumbnailView(item: item)
}
}
}
}
struct ThumbnailView: View {
var item: Item
@State var uiImage: UIImage?
var body: some View {
Rectangle().fill(.gray)
.aspectRatio(1.0, contentMode: .fit)
.overlay {
if let uiImage {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
}
}
.animation(.easeInOut, value: uiImage)
.task {
uiImage = await loadThumbnailFromDisk(for: item.id)
}
}
}
Share
Improve this question
asked Mar 28 at 4:21
RRRRRR
3151 gold badge3 silver badges12 bronze badges
2
|
1 Answer
Reset to default 0When dealing with a list of images, you probably want to use a lazy container list view.
In your code
ScrollView {
ForEach(images) { item in
ThumbnailView(item: item)
}
}
you create a static list embedded in a ScrollView, which creates all the item views a priory, and in your case, this would load and keep all images.
I would guess, that a transition(.opacity)
modifier would work (as loading all images at once) - but I also assume, this is not at all what you want.
You likely want to use a lazy container, for example a List
:
List(items) { item in
ItemView(item: item)
}
A List
only evaluates the body of the cell if it is visible.
If a cell was visible and now disappears, its @State
variable also get deallocated. This is unfortunate in your case, since it then also deallocates the stored image. A quick solution is to provide a suitable "model" for the cells.
The following code shows a quick solution, which can be run in preview or the simulator:
struct Item: Identifiable {
var id: String
var text: String?
}
struct ItemsView: View {
@State private var items: [Item] = Self.makeItems()
var body: some View {
NavigationStack {
List($items) { item in
ItemView(item: item)
}
}
.navigationTitle(Text("Items"))
}
static func makeItems() -> [Item] {
let items: [Item] = (0..<100).map { i in
Item(id: "\(i)")
}
return items
}
}
struct ItemView: View {
@Binding var item: Item
var body: some View {
HStack {
if let text = item.text {
Text(text)
.transition(.opacity)
} else {
Text("...")
}
}
.padding(20)
.task {
try? await Task.sleep(for: .seconds(1))
item.text = "Item \(item.id)"
}
}
}
You should notice the new structure Item
and how it is used as a binding and how the cell is modifying the bound value by "loading" the text value and assigning it the bound value. Notice also, that the array will keep the image (well, "text" here) - and that this might be problematic, when you have a large number of images! In this case, you should consider to use a library which can efficiently load and cache images.
So, this is not a great design, but it should demonstrate the issue.
Note also that it's using a transition
modifier, not an animation
modifier.
本文标签: Animating items in a SwiftUI list only at first appearanceStack Overflow
版权声明:本文标题:Animating items in a SwiftUI list only at first appearance - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744057213a2583472.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
Item
conform toIdentifiable
? This would need to be true and it is crucial. Also, you probably want to use atransition(.opacity)
modifier applied to yourImage
- not an animation. @AndreiG. When the task modifier executes,@State var uiImage
will always benil
. – CouchDeveloper Commented Mar 28 at 12:07