Fix unclickable elements
The introduction of iOS 18 brought a new bug that made `KFAnimatedImage` not recognize tap gestures and become unclickable. (https://github.com/onevcat/Kingfisher/issues/2295) This commit addresses the issue with a workaround found here: https://github.com/onevcat/Kingfisher/issues/2046#issuecomment-1554068070 The workaround was suggested by the author of the library to fix a slightly different issue, but that property seems to work for our purposes. The issue is addressed by adding a `contentShape` property to usages of `KFAnimatedImage`, in order to make them clickable. A custom modifier was created to make the solution less obscure and more obvious. Furthermore, one empty tap gesture handler was removed as it was preventing other tap gesture handlers on the image carousel from being triggered on iOS 18 Testing ------- PASS Configurations: - iPhone 13 mini on iOS 18.0 - iPhone SE simulator on iOS 17.5 Damus: This commit Coverage: - Check that the following views are clickable: - Images in the carousel - Profile picture on notes - Profile picture on thread comments - Profile picture on profile page Changelog-Fixed: Fix items that became unclickable on iOS 18 Closes: https://github.com/damus-io/damus/issues/2342 Closes: https://github.com/damus-io/damus/issues/2370 Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -1111,6 +1111,8 @@
|
|||||||
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
||||||
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
||||||
D7D2A3812BF815D000E4B42B /* PushNotificationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */; };
|
D7D2A3812BF815D000E4B42B /* PushNotificationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */; };
|
||||||
|
D7D68FF92C9E01BE0015A515 /* KFClickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D68FF82C9E01B60015A515 /* KFClickable.swift */; };
|
||||||
|
D7D68FFA2C9E01BE0015A515 /* KFClickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D68FF82C9E01B60015A515 /* KFClickable.swift */; };
|
||||||
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
||||||
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
||||||
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
||||||
@@ -1959,6 +1961,7 @@
|
|||||||
D7CBD1D32B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNotificationManagement.swift; sourceTree = "<group>"; };
|
D7CBD1D32B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNotificationManagement.swift; sourceTree = "<group>"; };
|
||||||
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleImpendingExpirationTests.swift; sourceTree = "<group>"; };
|
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleImpendingExpirationTests.swift; sourceTree = "<group>"; };
|
||||||
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationClient.swift; sourceTree = "<group>"; };
|
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationClient.swift; sourceTree = "<group>"; };
|
||||||
|
D7D68FF82C9E01B60015A515 /* KFClickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFClickable.swift; sourceTree = "<group>"; };
|
||||||
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
||||||
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
||||||
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
||||||
@@ -2540,6 +2543,7 @@
|
|||||||
4C75EFA227FA576C0006080F /* Views */ = {
|
4C75EFA227FA576C0006080F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D7D68FF72C9E01A80015A515 /* Utils */,
|
||||||
D78DB85D2C20FE9E00F0AB12 /* Chat */,
|
D78DB85D2C20FE9E00F0AB12 /* Chat */,
|
||||||
D71AC4CA2BA8E3320076268E /* Extensions */,
|
D71AC4CA2BA8E3320076268E /* Extensions */,
|
||||||
BA3759952ABCCF360018D73B /* Camera */,
|
BA3759952ABCCF360018D73B /* Camera */,
|
||||||
@@ -3368,6 +3372,14 @@
|
|||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D7D68FF72C9E01A80015A515 /* Utils */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D7D68FF82C9E01B60015A515 /* KFClickable.swift */,
|
||||||
|
);
|
||||||
|
path = Utils;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E06336A72B7582D600A88E6B /* Assets */ = {
|
E06336A72B7582D600A88E6B /* Assets */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -3803,6 +3815,7 @@
|
|||||||
B51C1CEA2B55A60A00E312A9 /* AddMuteItemView.swift in Sources */,
|
B51C1CEA2B55A60A00E312A9 /* AddMuteItemView.swift in Sources */,
|
||||||
4C5D5C992A6AF8F80024563C /* NdbNote.swift in Sources */,
|
4C5D5C992A6AF8F80024563C /* NdbNote.swift in Sources */,
|
||||||
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
||||||
|
D7D68FFA2C9E01BE0015A515 /* KFClickable.swift in Sources */,
|
||||||
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||||
D74AAFCF2B155D8C006CF0F4 /* ZapDataModel.swift in Sources */,
|
D74AAFCF2B155D8C006CF0F4 /* ZapDataModel.swift in Sources */,
|
||||||
@@ -4517,6 +4530,7 @@
|
|||||||
D73E5F332C6A97F4007EB227 /* ZapEvent.swift in Sources */,
|
D73E5F332C6A97F4007EB227 /* ZapEvent.swift in Sources */,
|
||||||
D73E5F342C6A97F4007EB227 /* TextEvent.swift in Sources */,
|
D73E5F342C6A97F4007EB227 /* TextEvent.swift in Sources */,
|
||||||
D73E5F352C6A97F4007EB227 /* WideEventView.swift in Sources */,
|
D73E5F352C6A97F4007EB227 /* WideEventView.swift in Sources */,
|
||||||
|
D7D68FF92C9E01BE0015A515 /* KFClickable.swift in Sources */,
|
||||||
D73E5F8A2C6AA69C007EB227 /* SideMenuView.swift in Sources */,
|
D73E5F8A2C6AA69C007EB227 /* SideMenuView.swift in Sources */,
|
||||||
D73E5F362C6A97F4007EB227 /* LongformView.swift in Sources */,
|
D73E5F362C6A97F4007EB227 /* LongformView.swift in Sources */,
|
||||||
D73E5F372C6A97F4007EB227 /* LongformPreview.swift in Sources */,
|
D73E5F372C6A97F4007EB227 /* LongformPreview.swift in Sources */,
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ struct ImageCarousel<Content: View>: View {
|
|||||||
Placeholder(url: url, geo_size: geo.size, num_urls: urls.count)
|
Placeholder(url: url, geo_size: geo.size, num_urls: urls.count)
|
||||||
}
|
}
|
||||||
.aspectRatio(contentMode: filling ? .fill : .fit)
|
.aspectRatio(contentMode: filling ? .fill : .fit)
|
||||||
|
.kfClickable()
|
||||||
.position(x: geo.size.width / 2, y: geo.size.height / 2)
|
.position(x: geo.size.width / 2, y: geo.size.height / 2)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Text(url.absoluteString)
|
Text(url.absoluteString)
|
||||||
@@ -274,8 +275,14 @@ struct ImageCarousel<Content: View>: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Medias
|
if #available(iOS 18.0, *) {
|
||||||
.onTapGesture { }
|
Medias
|
||||||
|
} else {
|
||||||
|
// An empty tap gesture recognizer is needed on iOS 17 and below to suppress other overlapping tap recognizers
|
||||||
|
// Otherwise it will both open the carousel and go to a note at the same time
|
||||||
|
Medias.onTapGesture { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if urls.count > 1 {
|
if urls.count > 1 {
|
||||||
PageControlView(currentPage: $model.selectedIndex, numberOfPages: urls.count)
|
PageControlView(currentPage: $model.selectedIndex, numberOfPages: urls.count)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ struct EditBannerImageView: View {
|
|||||||
Color(uiColor: .secondarySystemBackground)
|
Color(uiColor: .secondarySystemBackground)
|
||||||
}
|
}
|
||||||
.onFailureImage(defaultImage)
|
.onFailureImage(defaultImage)
|
||||||
|
.kfClickable()
|
||||||
|
|
||||||
EditPictureControl(uploader: damus_state.settings.default_media_uploader, pubkey: damus_state.pubkey, image_url: $banner_image, uploadObserver: viewModel, callback: callback)
|
EditPictureControl(uploader: damus_state.settings.default_media_uploader, pubkey: damus_state.pubkey, image_url: $banner_image, uploadObserver: viewModel, callback: callback)
|
||||||
}
|
}
|
||||||
@@ -54,6 +55,7 @@ struct InnerBannerImageView: View {
|
|||||||
Color(uiColor: .secondarySystemBackground)
|
Color(uiColor: .secondarySystemBackground)
|
||||||
}
|
}
|
||||||
.onFailureImage(defaultImage)
|
.onFailureImage(defaultImage)
|
||||||
|
.kfClickable()
|
||||||
} else {
|
} else {
|
||||||
Image(uiImage: defaultImage).resizable()
|
Image(uiImage: defaultImage).resizable()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ struct HighlightEventRef: View {
|
|||||||
FailedImage()
|
FailedImage()
|
||||||
}
|
}
|
||||||
.frame(width: 35, height: 35)
|
.frame(width: 35, height: 35)
|
||||||
|
.kfClickable()
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(.gray.opacity(0.5), lineWidth: 0.5))
|
.overlay(RoundedRectangle(cornerRadius: 10).stroke(.gray.opacity(0.5), lineWidth: 0.5))
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ struct HighlightLink: View {
|
|||||||
.background(DamusColors.adaptableWhite)
|
.background(DamusColors.adaptableWhite)
|
||||||
}
|
}
|
||||||
.frame(width: 35, height: 35)
|
.frame(width: 35, height: 35)
|
||||||
|
.kfClickable()
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ struct LongformPreviewBody: View {
|
|||||||
}
|
}
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(maxWidth: .infinity, maxHeight: header ? .infinity : 150)
|
.frame(maxWidth: .infinity, maxHeight: header ? .infinity : 150)
|
||||||
|
.kfClickable()
|
||||||
.cornerRadius(1)
|
.cornerRadius(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ struct ImageContainerView: View {
|
|||||||
view.framePreloadCount = 3
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
.imageModifier(ImageHandler(handler: $image))
|
.imageModifier(ImageHandler(handler: $image))
|
||||||
|
.kfClickable()
|
||||||
.clipped()
|
.clipped()
|
||||||
.modifier(ImageContextMenuModifier(url: url, image: image, settings: settings, showShareSheet: $showShareSheet))
|
.modifier(ImageContextMenuModifier(url: url, image: image, settings: settings, showShareSheet: $showShareSheet))
|
||||||
.sheet(isPresented: $showShareSheet) {
|
.sheet(isPresented: $showShareSheet) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ struct ProfileImageContainerView: View {
|
|||||||
.imageModifier(ImageHandler(handler: $image))
|
.imageModifier(ImageHandler(handler: $image))
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.modifier(ImageContextMenuModifier(url: url, image: image, settings: settings, showShareSheet: $showShareSheet))
|
.modifier(ImageContextMenuModifier(url: url, image: image, settings: settings, showShareSheet: $showShareSheet))
|
||||||
|
.kfClickable()
|
||||||
.sheet(isPresented: $showShareSheet) {
|
.sheet(isPresented: $showShareSheet) {
|
||||||
ShareSheet(activityItems: [url])
|
ShareSheet(activityItems: [url])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ struct EditPictureControl: View {
|
|||||||
}
|
}
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: (size ?? 25) + 10, height: (size ?? 25) + 10)
|
.frame(width: (size ?? 25) + 10, height: (size ?? 25) + 10)
|
||||||
|
.kfClickable()
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(DamusColors.white)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.overlay(Circle().stroke(.white, lineWidth: 4))
|
.overlay(Circle().stroke(.white, lineWidth: 4))
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ struct InnerProfilePicView: View {
|
|||||||
}
|
}
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
|
.kfClickable()
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ struct EditProfilePictureView: View {
|
|||||||
view.framePreloadCount = 3
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
|
.kfClickable()
|
||||||
|
|
||||||
EditPictureControl(uploader: damus_state?.settings.default_media_uploader ?? .nostrBuild, pubkey: pubkey, image_url: $profile_url, uploadObserver: uploadObserver, callback: callback)
|
EditPictureControl(uploader: damus_state?.settings.default_media_uploader ?? .nostrBuild, pubkey: pubkey, image_url: $profile_url, uploadObserver: uploadObserver, callback: callback)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ struct InnerRelayPicView: View {
|
|||||||
Placeholder(url: url)
|
Placeholder(url: url)
|
||||||
}
|
}
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
|
.kfClickable()
|
||||||
} else {
|
} else {
|
||||||
FailedRelayImage(url: nil)
|
FailedRelayImage(url: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
23
damus/Views/Utils/KFClickable.swift
Normal file
23
damus/Views/Utils/KFClickable.swift
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// ClickableOverlay.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2024-09-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Applies a property that makes `KFAnimatedImage` clickable again on iOS 18+
|
||||||
|
fileprivate struct KFClickable: ViewModifier {
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
/// Applies a property that makes `KFAnimatedImage` clickable again on iOS 18+
|
||||||
|
func kfClickable() -> some View {
|
||||||
|
return self.modifier(KFClickable())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user