Last active
December 31, 2024 11:53
-
-
Save Snowy1803/c077b60190c2543a823526a0c73ff142 to your computer and use it in GitHub Desktop.
This code allows you to use matchedGeometryEffect in SwiftUI while keeping iOS 13 compatibility in your app.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // ContentView.swift | |
| // Example of using matchedGeometryEffect in iOS 13 code | |
| // matchedGeometryEffect example code taken and adapted from : | |
| // https://sarunw.com/posts/a-first-look-at-matchedgeometryeffect/ | |
| // | |
| // Created by Emil Pedersen on 16/10/2020. | |
| // | |
| struct ContentView: View { | |
| @State private var isExpanded = false | |
| var body: some View { | |
| Group { | |
| if isExpanded { | |
| VStack { | |
| RoundedRectangle(cornerRadius: 10) | |
| .foregroundColor(Color.pink) | |
| .frame(width: 60, height: 60) | |
| .namespacedMatchedGeometryEffect(id: "rect") | |
| Text("Hello SwiftUI!").fontWeight(.semibold) | |
| .namespacedMatchedGeometryEffect(id: "text") | |
| } | |
| } else { | |
| HStack { | |
| Text("Hello SwiftUI!").fontWeight(.semibold) | |
| .namespacedMatchedGeometryEffect(id: "text") | |
| RoundedRectangle(cornerRadius: 10) | |
| .foregroundColor(Color.pink) | |
| .frame(width: 60, height: 60) | |
| .namespacedMatchedGeometryEffect(id: "rect") | |
| } | |
| } | |
| }.onTapGesture { | |
| withAnimation { | |
| isExpanded.toggle() | |
| } | |
| }.namespaced() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // NamespaceWrapper.swift | |
| // NamespaceWrapper for using matchedGeometryEffect in iOS 13 code | |
| // | |
| // Created by Emil Pedersen on 16/10/2020. | |
| // | |
| import SwiftUI | |
| @available(iOS 14, *) | |
| struct NamespaceWrapper<Content: View>: View { | |
| @Namespace var namespace | |
| var content: Content | |
| var body: some View { | |
| content.environment(\.namespace, namespace) | |
| } | |
| } | |
| @available(iOS 14, *) | |
| struct NamespaceReader<Content: View, ID: Hashable>: View { | |
| @Environment(\.namespace) var namespace | |
| var content: Content | |
| var id: ID | |
| var anchor: UnitPoint = .center | |
| var isSource: Bool = true | |
| var body: some View { | |
| content.matchedGeometryEffect(id: id, in: namespace!, anchor: anchor, isSource: isSource) | |
| } | |
| } | |
| @available(iOS 14, *) | |
| struct NamespaceKey: EnvironmentKey { | |
| static let defaultValue: Namespace.ID? = nil | |
| } | |
| @available(iOS 14, *) | |
| extension EnvironmentValues { | |
| var namespace: Namespace.ID? { | |
| get { | |
| self[NamespaceKey.self] | |
| } | |
| set { | |
| self[NamespaceKey.self] = newValue | |
| } | |
| } | |
| } | |
| extension View { | |
| func namespaced() -> AnyView { | |
| if #available(iOS 14, *) { | |
| return AnyView(NamespaceWrapper(content: self)) | |
| } else { | |
| return AnyView(self) | |
| } | |
| } | |
| func namespacedMatchedGeometryEffect<ID>(id: ID, anchor: UnitPoint = .center, isSource: Bool = true) -> some View where ID : Hashable { | |
| if #available(iOS 14, *) { | |
| return AnyView(NamespaceReader(content: self, id: id, anchor: anchor, isSource: isSource)) | |
| } else { | |
| return AnyView(self) | |
| } | |
| } | |
| } |
That's working well. Thank you for saving a lot of time!
I actually have a library β https://github.com/shaps80/SwiftUIBackports β that includes many full backports and will soon include support for this as well, as in the same feature will work in iOS 13 as well. Almost all backports work on the expected platforms as well (i.e. iOS, macOS, tvOS, watchOS)
Can I also suggest a couple small improvements regardless. You can drop the AnyView which is less performant and problematic at times with things like animation.
@ViewBuilder // you're missing this
func namespaced() -> some View {
if #available(iOS 14, *) {
NamespaceWrapper(content: self)
} else {
self
}
}
@ViewBuilder
func namespacedMatchedGeometryEffect<ID>(id: ID, anchor: UnitPoint = .center, isSource: Bool = true) -> some View where ID : Hashable {
if #available(iOS 14, *) {
NamespaceReader(content: self, id: id, anchor: anchor, isSource: isSource)
} else {
self
}
}
Also in NamespaceReader you have namespace! β you could instead just set your defaultValue in your EnvironmentKey to a default instance and get rid of the potential crash π
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice job, thanks for sharing this!