SwiftUI 中的自定义模式转换
Custom modal transitions in SwiftUI
我正在尝试使用 SwiftUI 重新创建 iOS 11/12 App Store。
假设 "story" 是点击卡片时显示的视图。
我已经完成了卡片,但我现在遇到的问题是如何制作动画以显示 "story"。
由于我不擅长解释,这里有一个gif:
我曾想过将整个卡片设为 PresentationLink,但 "story" 显示为模态,因此它不会覆盖整个屏幕,也不会执行我想要的动画。
最相似的是 NavigationLink,但是这迫使我添加一个 NavigationView,并且卡片显示为另一个页面。
我实际上不在乎它是 PresentationLink 还是 NavigationLink 或其他任何东西,只要它执行动画并显示 "story"。
提前致谢。
我的代码:
Card.swift
struct Card: View {
var icon: UIImage = UIImage(named: "flappy")!
var cardTitle: String = "Welcome to \nCards!"
var cardSubtitle: String = ""
var itemTitle: String = "Flappy Bird"
var itemSubtitle: String = "Flap That!"
var cardCategory: String = ""
var textColor: UIColor = UIColor.white
var background: String = ""
var titleColor: Color = .black
var backgroundColor: Color = .white
var body: some View {
VStack {
if background != "" {
Image(background)
.resizable()
.frame(width: 380, height: 400)
.cornerRadius(20)
} else {
RoundedRectangle(cornerRadius: 20)
.frame(width: 400, height: 400)
.foregroundColor(backgroundColor)
}
VStack {
HStack {
VStack(alignment: .leading) {
if cardCategory != "" {
Text(verbatim: cardCategory.uppercased())
.font(.headline)
.fontWeight(.heavy)
.opacity(0.3)
.foregroundColor(titleColor)
//.opacity(1)
}
HStack {
Text(verbatim: cardTitle)
.font(.largeTitle)
.fontWeight(.heavy)
.lineLimit(3)
.foregroundColor(titleColor)
}
}
Spacer()
}.offset(y: -390)
.padding(.bottom, -390)
HStack {
if cardSubtitle != "" {
Text(verbatim: cardSubtitle)
.font(.system(size: 17))
.foregroundColor(titleColor)
}
Spacer()
}
.offset(y: -50)
.padding(.bottom, -50)
}
.padding(.leading)
}.padding(.leading).padding(.trailing)
}
}
所以
Card(cardSubtitle: "Welcome to this library I made :p", cardCategory: "CONNECT", background: "flBackground", titleColor: .white)
显示:
SwiftUI 目前不执行自定义模式转换,因此我们必须使用变通方法。
我能想到的一种方法是使用 ZStack
自己进行演示。可以使用 GeometryReader
获取源帧。然后,可以使用框架和位置修改器来控制目标形状。
开始时,目标将被设置为与源的位置和大小完全匹配。然后紧接着,目的地将在动画块中设置为全屏大小。
struct ContentView: View {
@State var isPresenting = false
@State var isFullscreen = false
@State var sourceRect: CGRect? = nil
var body: some View {
ZStack {
GeometryReader { proxy in
Button(action: {
self.isFullscreen = false
self.isPresenting = true
self.sourceRect = proxy.frame(in: .global)
}) { ... }
}
if isPresenting {
GeometryReader { proxy in
ModalView()
.frame(
width: self.isFullscreen ? nil : self.sourceRect?.width ?? nil,
height: self.isFullscreen ? nil : self.sourceRect?.height ?? nil)
.position(
self.isFullscreen ? proxy.frame(in: .global).center :
self.sourceRect?.center ?? proxy.frame(in: .global).center)
.onAppear {
withAnimation {
self.isFullscreen = true
}
}
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
extension CGRect {
var center : CGPoint {
return CGPoint(x:self.midX, y:self.midY)
}
}
iOS/tvOS14 和 macOS 11 中的 SwiftUI matchedGeometryEffect(id:in:properties:anchor:isSource:)
可以在不同层次结构之间设置动画视图转换。
Link to Official Documentation
这是一个最小的例子:
struct SomeView: View {
@State var isPresented = false
@Namespace var namespace
var body: some View {
VStack {
Button(action: {
withAnimation {
self.isPresented.toggle()
}
}) {
Text("Toggle")
}
SomeSourceContainer {
MatchedView()
.matchedGeometryEffect(id: "UniqueViewID", in: namespace, properties: .frame, isSource: !isPresented)
}
if isPresented {
SomeTargetContainer {
MatchedTargetView()
.matchedGeometryEffect(id: "UniqueViewID", in: namespace, properties: .frame, isSource: isPresented)
}
}
}
}
}
我正在尝试使用 SwiftUI 重新创建 iOS 11/12 App Store。 假设 "story" 是点击卡片时显示的视图。
我已经完成了卡片,但我现在遇到的问题是如何制作动画以显示 "story"。
由于我不擅长解释,这里有一个gif:
我曾想过将整个卡片设为 PresentationLink,但 "story" 显示为模态,因此它不会覆盖整个屏幕,也不会执行我想要的动画。
最相似的是 NavigationLink,但是这迫使我添加一个 NavigationView,并且卡片显示为另一个页面。
我实际上不在乎它是 PresentationLink 还是 NavigationLink 或其他任何东西,只要它执行动画并显示 "story"。
提前致谢。
我的代码:
Card.swift
struct Card: View {
var icon: UIImage = UIImage(named: "flappy")!
var cardTitle: String = "Welcome to \nCards!"
var cardSubtitle: String = ""
var itemTitle: String = "Flappy Bird"
var itemSubtitle: String = "Flap That!"
var cardCategory: String = ""
var textColor: UIColor = UIColor.white
var background: String = ""
var titleColor: Color = .black
var backgroundColor: Color = .white
var body: some View {
VStack {
if background != "" {
Image(background)
.resizable()
.frame(width: 380, height: 400)
.cornerRadius(20)
} else {
RoundedRectangle(cornerRadius: 20)
.frame(width: 400, height: 400)
.foregroundColor(backgroundColor)
}
VStack {
HStack {
VStack(alignment: .leading) {
if cardCategory != "" {
Text(verbatim: cardCategory.uppercased())
.font(.headline)
.fontWeight(.heavy)
.opacity(0.3)
.foregroundColor(titleColor)
//.opacity(1)
}
HStack {
Text(verbatim: cardTitle)
.font(.largeTitle)
.fontWeight(.heavy)
.lineLimit(3)
.foregroundColor(titleColor)
}
}
Spacer()
}.offset(y: -390)
.padding(.bottom, -390)
HStack {
if cardSubtitle != "" {
Text(verbatim: cardSubtitle)
.font(.system(size: 17))
.foregroundColor(titleColor)
}
Spacer()
}
.offset(y: -50)
.padding(.bottom, -50)
}
.padding(.leading)
}.padding(.leading).padding(.trailing)
}
}
所以
Card(cardSubtitle: "Welcome to this library I made :p", cardCategory: "CONNECT", background: "flBackground", titleColor: .white)
显示:
SwiftUI 目前不执行自定义模式转换,因此我们必须使用变通方法。
我能想到的一种方法是使用 ZStack
自己进行演示。可以使用 GeometryReader
获取源帧。然后,可以使用框架和位置修改器来控制目标形状。
开始时,目标将被设置为与源的位置和大小完全匹配。然后紧接着,目的地将在动画块中设置为全屏大小。
struct ContentView: View {
@State var isPresenting = false
@State var isFullscreen = false
@State var sourceRect: CGRect? = nil
var body: some View {
ZStack {
GeometryReader { proxy in
Button(action: {
self.isFullscreen = false
self.isPresenting = true
self.sourceRect = proxy.frame(in: .global)
}) { ... }
}
if isPresenting {
GeometryReader { proxy in
ModalView()
.frame(
width: self.isFullscreen ? nil : self.sourceRect?.width ?? nil,
height: self.isFullscreen ? nil : self.sourceRect?.height ?? nil)
.position(
self.isFullscreen ? proxy.frame(in: .global).center :
self.sourceRect?.center ?? proxy.frame(in: .global).center)
.onAppear {
withAnimation {
self.isFullscreen = true
}
}
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
extension CGRect {
var center : CGPoint {
return CGPoint(x:self.midX, y:self.midY)
}
}
iOS/tvOS14 和 macOS 11 中的 SwiftUI matchedGeometryEffect(id:in:properties:anchor:isSource:)
可以在不同层次结构之间设置动画视图转换。
Link to Official Documentation
这是一个最小的例子:
struct SomeView: View {
@State var isPresented = false
@Namespace var namespace
var body: some View {
VStack {
Button(action: {
withAnimation {
self.isPresented.toggle()
}
}) {
Text("Toggle")
}
SomeSourceContainer {
MatchedView()
.matchedGeometryEffect(id: "UniqueViewID", in: namespace, properties: .frame, isSource: !isPresented)
}
if isPresented {
SomeTargetContainer {
MatchedTargetView()
.matchedGeometryEffect(id: "UniqueViewID", in: namespace, properties: .frame, isSource: isPresented)
}
}
}
}
}