matchedGeometryEffect是SwiftUI提供的一个强大的工具,它能够在视图层次结构中不同位置的视图之间创建平滑的动画过渡。
-
1、id:用于唯一标识匹配的视图。只有当两个视图使用相同的id时matchedGeometryEffect才能在它们之间创建动画过渡。
-
2、namespace:命名空间,用于将不同视图的动画过渡关联起来。确保只有标记了相同命名空间的视图之间才会应用动画效果。
import SwiftUI
// Model Data
struct CatModel: Identifiable {
var id = UUID().uuidString
var image: String
var title: String
var price: String
}
var cats = [
CatModel(image: "cat1", title: "cat1", price: "$100"),
CatModel(image: "cat2", title: "cat2", price: "$200"),
CatModel(image: "cat3", title: "cat3", price: "$300"),
CatModel(image: "cat4", title: "cat4", price: "$400"),
CatModel(image: "cat5", title: "cat5", price: "$500"),
CatModel(image: "cat6", title: "cat6", price: "$600"),
]
// Scrolling Tab Button
var CatTabs = ["cat1","cat2","cat3","cat4"]
import SwiftUI
struct CatView: View {
var catData: CatModel
var animation: Namespace.ID
var body: some View {
VStack(alignment: .leading, spacing: 6) {
ZStack {
// both color and image are same name
Color(catData.image)
.cornerRadius(15)
Image(catData.image)
.resizable()
.aspectRatio(contentMode: .fit)
.padding(20)
.matchedGeometryEffect(id: catData.image, in: animation)
}
Text(catData.title)
.fontWeight(.heavy)
.foregroundColor(.gray)
Text(catData.price)
.fontWeight(.heavy)
.foregroundColor(.black)
}
}
}
import SwiftUI
struct CatTabButton: View {
var title: String
@Binding var selectedCat: String
var animation: Namespace.ID
var body: some View {
Button {
withAnimation(.spring()) {
selectedCat = title
}
}label: {
VStack(alignment: .leading, spacing: 6) {
Text(title)
.fontWeight(.heavy)
.foregroundColor(selectedCat == title ? .black : .gray)
if selectedCat == title {
Capsule()
.fill(Color.black)
.frame(width: 40, height: 4)
.matchedGeometryEffect(id: "Tab", in: animation)
}
}
.frame(width: 100)
}
}
}
import SwiftUI
struct ColorButton: View {
var color: Color
@Binding var selectedColor: Color
var body: some View {
ZStack {
Circle()
.fill(color)
.frame(width: 20, height: 20)
Circle()
.stroke(Color.black.opacity(selectedColor == color ? 1 : 0), lineWidth: 1)
.frame(width: 30, height: 30)
}
.onTapGesture {
withAnimation {
selectedColor = color
}
}
}
}
import SwiftUI
struct CustomCorner: Shape {
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 35, height: 35))
return Path(path.cgPath)
}
}
-
1、定义
CustomCorner
结构:struct CustomCorner: Shape
:定义一个名为CustomCorner
的结构体,并声明它遵循Shape
协议。此协议要求实现path(in:)
方法。 -
2、实现
path(in:)
方法:func path(in rect: CGRect) -> Path
:这个方法接收一个CGRect
参数,表示要绘制形状的区域,并返回一个Path
对象。 -
3、使用
UIBezierPath
创建一个圆角矩形:let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 35, height: 35))
:-
(1)cornerRadii:CGSize(width: 35, height: 35):
设置圆角半径,宽度和高度均为 35 点。 -
(2)byRoundingCorners: [.topLeft, .topRight]
:指明哪些角需要圆角处理。在这里,只有左上角和右上角会被圆角化。 -
(3)roundedRect: rect
:指定矩形的边界。
-
-
4、返回 Path:
return Path(path.cgPath)
:将UIBezierPath
转换为 SwiftUI 的Path
类型并返回。这样就可以在 SwiftUI 中使用这个自定义形状。
import SwiftUI
struct ShopHome: View {
@State var selectedCat = CatTabs[0]
@Namespace var animation
@State var show = false
@State var selectedcat: CatModel!
var body: some View {
ZStack {
VStack(spacing: 0) {
ZStack {
HStack(spacing: 15) {
Button {
}label:{
Image(systemName: "line.3.horizontal.decrease")
.font(.title)
.foregroundColor(.black)
}
Spacer(minLength: 0)
Button {
}label:{
Image(systemName: "magnifyingglass")
.font(.title)
.foregroundColor(.black)
}
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
Button {
}label:{
Image(systemName: "cart")
.font(.title)
.foregroundColor(.black)
}
Circle()
.fill(Color.red)
.frame(width: 15, height: 15)
.offset(x: 5, y: -10)
}
}
Text("Shop")
.font(.title)
.fontWeight(.heavy)
.foregroundColor(.black)
}
.padding()
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
.background(Color.white)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 5)
ScrollView(.vertical, showsIndicators: false) {
VStack {
HStack {
Text("Cat")
.font(.title)
.fontWeight(.heavy)
.foregroundColor(.black)
Spacer(minLength: 0)
}
.padding(.horizontal)
.padding(.top)
.padding(.bottom, 10)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 15) {
ForEach(CatTabs, id: \.self) { tab in
CatTabButton(title: tab, selectedCat: $selectedCat, animation: animation)
}
}
.padding(.horizontal)
.padding(.top, 10)
}
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 15), count: 2), spacing: 15) {
ForEach(cats) { cat in
CatView(catData: cat, animation: animation)
.onTapGesture {
withAnimation(.easeIn) {
selectedcat = cat
show.toggle()
}
}
}
}
.padding()
.padding(.top, 10)
}
}
}
.background(Color.black.opacity(0.05).ignoresSafeArea(.all, edges: .all))
if selectedcat != nil && show {
CatDetailView(catData: $selectedcat, show: $show, animation: animation)
}
}
.ignoresSafeArea(.all, edges: .top)
}
}
struct ShopHome_Previews: PreviewProvider {
static var previews: some View {
ShopHome()
}
}
import SwiftUI
struct CatDetailView: View {
@Binding var catData: CatModel!
@Binding var show: Bool
var animation: Namespace.ID
@State var selectedColor = Color.red
@State var count = 0
@State var isSmallDevice = UIScreen.main.bounds.height < 750
var body: some View {
VStack {
HStack {
VStack(alignment: .leading, spacing: 5) {
Button {
withAnimation(.easeOut) {
show.toggle()
}
}label: {
Image(systemName: "chevron.left")
.font(.title)
.foregroundColor(.white)
}
Text("Different Cat")
.fontWeight(.bold)
.foregroundColor(.white)
.padding(.top)
Text(catData.title)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.white)
}
Spacer(minLength: 0)
Button {
}label: {
Image(systemName: "cart")
.font(.title)
.foregroundColor(.white)
}
}
.padding()
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
HStack(spacing: 10) {
VStack(alignment: .leading, spacing: 6) {
Text("Price")
.fontWeight(.bold)
.foregroundColor(.white)
Text(catData.price)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.white)
}
Image(catData.image)
.resizable()
.aspectRatio(contentMode: .fit)
.matchedGeometryEffect(id: catData.image, in: animation)
}
.padding()
.padding(.top, 10)
.zIndex(1)
VStack {
ScrollView(isSmallDevice ? .vertical : .init(), showsIndicators: false) {
HStack{
VStack(alignment: .leading, spacing: 8) {
Text("Color")
.fontWeight(.bold)
.foregroundColor(.gray)
HStack(spacing: 15) {
ColorButton(color: Color(catData.image), selectedColor: $selectedColor)
ColorButton(color: Color.green, selectedColor: $selectedColor)
ColorButton(color: Color.yellow, selectedColor: $selectedColor)
}
}
Spacer(minLength: 0)
VStack(alignment: .leading, spacing: 8) {
Text("Size")
.font(.title)
.fontWeight(.semibold)
.foregroundColor(.black)
Text("12 cm")
.fontWeight(.heavy)
.foregroundColor(.black)
}
}
.padding(.horizontal)
.padding(.top, isSmallDevice ? 0 : -6)
Text("I like cat, Mygfgf family..hhh..d.sss.ds.dddafdsdsf.ds.dsfsdgs..dfs..gfdhgfj..hdj.hkdd")
.foregroundColor(.gray)
.multilineTextAlignment(.leading)
.padding()
HStack(spacing: 20){
Button {
if count > 0 {
count -= 1
}
}label: {
Image(systemName: "minus")
.font(.title2)
.foregroundColor(.gray)
.frame(width: 35, height: 35)
.background(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
}
Text("\(count)")
.font(.title2)
Button {
count += 1
}label: {
Image(systemName: "plus")
.font(.title2)
.foregroundColor(.gray)
.frame(width: 35, height: 35)
.background(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
}
Spacer()
Button {
}label: {
Image(systemName: "suit.heart.fill")
.font(.title2)
.foregroundColor(.white)
.padding(10)
.background(Color.red)
.clipShape(Circle())
}
}
.padding(.horizontal)
Spacer(minLength: 0)
Button {
}label: {
Text("BUY CAT")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
.padding(.vertical)
.frame(width: UIScreen.main.bounds.width - 30)
.background(Color(catData.image))
.clipShape(Capsule())
}
.padding(.top)
.padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom == 0 ? 15 : 0)
}
}
.background(
Color.white
.clipShape(CustomCorner())
.padding(.top, isSmallDevice ? -60 : -100)
)
.zIndex(0)
}
.background(Color(catData.image).ignoresSafeArea(.all, edges: .top))
.background(Color.white.ignoresSafeArea(.all, edges: .bottom))
.onAppear {
// First Color is image or catcolor
selectedColor = Color(catData.image)
}
}
}