commit b1a2b471161c6d63afa355eb56068b3d1910dc76
parent af6f88ab175dd6aa200dad71b7d61a4eaf5de371
Author: William Casarin <jb55@jb55.com>
Date: Tue, 21 Feb 2023 04:34:52 -0800
Eliminate popping when scrolling
This commit makes a few changes:
- Link preview views are no longer cached, only the metadata. This fixes
a memory leak when preview videos. It will keep playing the video
forever eventually leading to a crash. This is fixed!
- Cache the intrinsic height of previews, when loading notes it looks
for the cached height so that things don't pop-in after the fact
- Note artifacts and previews are set in the constructor instead of
onAppear, this prevents the size from changing and popping after it
has been loaded into the lazyvstack
Changelog-Fixed: Fix memory leak with inline videos
Changelog-Fixed: Eliminate popping when scrolling
Diffstat:
4 files changed, 74 insertions(+), 28 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -1649,7 +1649,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 3;
+ CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -1691,7 +1691,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 3;
+ CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
diff --git a/damus/Util/LinkView.swift b/damus/Util/LinkView.swift
@@ -10,10 +10,11 @@ import LinkPresentation
class CustomLinkView: LPLinkView {
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
+
}
enum Metadata {
- case linkmeta(LPLinkMetadata)
+ case linkmeta(CachedMetadata)
case url(URL)
}
@@ -26,12 +27,19 @@ struct LinkViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> CustomLinkView {
switch meta {
case .linkmeta(let linkmeta):
- return CustomLinkView(metadata: linkmeta)
+ return CustomLinkView(metadata: linkmeta.meta)
case .url(let url):
return CustomLinkView(url: url)
}
}
func updateUIView(_ uiView: CustomLinkView, context: Context) {
+ switch meta {
+ case .linkmeta(let cached):
+ cached.intrinsic_height = uiView.intrinsicContentSize.height
+ case .url:
+ return
+ }
+
}
}
diff --git a/damus/Util/PreviewCache.swift b/damus/Util/PreviewCache.swift
@@ -8,8 +8,18 @@
import Foundation
import LinkPresentation
+class CachedMetadata {
+ let meta: LPLinkMetadata
+ var intrinsic_height: CGFloat?
+
+ init(meta: LPLinkMetadata) {
+ self.meta = meta
+ self.intrinsic_height = nil
+ }
+}
+
enum Preview {
- case value(LinkViewRepresentable)
+ case value(CachedMetadata)
case failed
}
@@ -20,12 +30,12 @@ class PreviewCache {
return previews[evid]
}
- func store(evid: String, preview: LinkViewRepresentable?) {
+ func store(evid: String, preview: LPLinkMetadata?) {
switch preview {
case .none:
previews[evid] = .failed
case .some(let meta):
- previews[evid] = .value(meta)
+ previews[evid] = .value(CachedMetadata(meta: meta))
}
}
diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift
@@ -27,9 +27,21 @@ struct NoteContentView: View {
let event: NostrEvent
let show_images: Bool
let size: EventViewKind
+ let preview_height: CGFloat?
@State var artifacts: NoteArtifacts
- @State var preview: LinkViewRepresentable? = nil
+ @State var preview: LinkViewRepresentable?
+
+ init(damus_state: DamusState, event: NostrEvent, show_images: Bool, size: EventViewKind, artifacts: NoteArtifacts) {
+ self.damus_state = damus_state
+ self.event = event
+ self.show_images = show_images
+ self.size = size
+ self._artifacts = State(initialValue: artifacts)
+ self.preview_height = lookup_cached_preview_size(previews: damus_state.previews, evid: event.id)
+ self._preview = State(initialValue: load_cached_preview(previews: damus_state.previews, evid: event.id))
+ self._artifacts = State(initialValue: render_note_content(ev: event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey))
+ }
func MainContent() -> some View {
return VStack(alignment: .leading) {
@@ -58,23 +70,21 @@ struct NoteContentView: View {
}
if let preview = self.preview, show_images {
- preview
- } else {
- ForEach(artifacts.links, id:\.self) { link in
- if let url = link {
- LinkViewRepresentable(meta: .url(url))
- .frame(height: 50)
- }
+ if let preview_height {
+ preview
+ .frame(height: preview_height)
+ } else {
+ preview
}
+ } else if let link = artifacts.links.first {
+ LinkViewRepresentable(meta: .url(link))
+ .frame(height: 50)
}
}
}
var body: some View {
MainContent()
- .onAppear() {
- self.artifacts = render_note_content(ev: event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
- }
.onReceive(handle_notify(.profile_updated)) { notif in
let profile = notif.object as! ProfileUpdate
let blocks = event.blocks(damus_state.keypair.privkey)
@@ -92,21 +102,19 @@ struct NoteContentView: View {
}
}
.task {
- if let preview = damus_state.previews.lookup(self.event.id) {
- switch preview {
- case .value(let view):
- self.preview = view
- case .failed:
- // don't try to refetch meta if we've failed
- return
- }
+ guard self.preview == nil else {
+ return
}
if show_images, artifacts.links.count == 1 {
let meta = await getMetaData(for: artifacts.links.first!)
- let view = meta.map { LinkViewRepresentable(meta: .linkmeta($0)) }
- damus_state.previews.store(evid: self.event.id, preview: view)
+ damus_state.previews.store(evid: self.event.id, preview: meta)
+ guard case .value(let cached) = damus_state.previews.lookup(self.event.id) else {
+ return
+ }
+ let view = LinkViewRepresentable(meta: .linkmeta(cached))
+
self.preview = view
}
@@ -233,3 +241,23 @@ func is_image_url(_ url: URL) -> Bool {
return str.hasSuffix("png") || str.hasSuffix("jpg") || str.hasSuffix("jpeg") || str.hasSuffix("gif")
}
+func lookup_cached_preview_size(previews: PreviewCache, evid: String) -> CGFloat? {
+ guard case .value(let cached) = previews.lookup(evid) else {
+ return nil
+ }
+
+ guard let height = cached.intrinsic_height else {
+ return nil
+ }
+
+ return height
+}
+
+
+func load_cached_preview(previews: PreviewCache, evid: String) -> LinkViewRepresentable? {
+ guard case .value(let meta) = previews.lookup(evid) else {
+ return nil
+ }
+
+ return LinkViewRepresentable(meta: .linkmeta(meta))
+}