我有一个基本视图,尝试将单个图像动画化为图像的选项卡视图。我使用下面的代码遇到一些动画问题。另外,在底部滚动视图中,图像在前缘上对齐,如何在不使每个图像与屏幕全宽的情况下将它们对齐?
复制可粘贴的工作代码,(您需要 king Fisher,否则使用 asyncimage):
import SwiftUI
import Kingfisher
struct TopPostMediaView: View {
@State var isExpanded: Bool = false
@Namespace var animation
@State var detailScrollPosition: UUID? = nil
@State var animMid = ""
var body: some View {
GeometryReader {
let size = $0.size
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(mynewtemp) { pic in
LazyHStack {
let mid = pic.photo + pic.id.uuidString
if !isExpanded || animMid != mid {
KFImage(URL(string: pic.photo))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: size.width)
.clipShape(RoundedRectangle(cornerRadius: 10))
.contentShape(RoundedRectangle(cornerRadius: 10))
.matchedGeometryEffect(id: mid, in: animation)
.onTapGesture {
//move to the image
detailScrollPosition = pic.id
//sub view id to animate up
animMid = mid
//present top view
withAnimation(.easeInOut(duration: 0.15)){
isExpanded = true
}
}
} else {
KFImage(URL(string: pic.photo))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: size.width)
.clipShape(RoundedRectangle(cornerRadius: 10))
.contentShape(RoundedRectangle(cornerRadius: 10))
.opacity(0.0)
}
}
.frame(maxWidth: size.width).frame(height: size.height)
.contentShape(.rect)
}
}.scrollTargetLayout()
}
.scrollPosition(id: $detailScrollPosition, anchor: .center)
.scrollIndicators(.hidden)
.scrollTargetBehavior(.viewAligned)
.scrollClipDisabled()
}
.frame(height: 250)
.overlay {
if isExpanded {
ZStack {
Color.black.frame(height: viewHeight() + 30.0).clipShape(RoundedRectangle(cornerRadius: 40))
ScrollView(.horizontal) {
LazyHStack(spacing: 0) {
ForEach(mynewtemp) { pic in
KFImage(URL(string: pic.photo))
.resizable()
.aspectRatio(contentMode: .fit)
.containerRelativeFrame(.horizontal)
.clipped()
}
}.scrollTargetLayout()
}
.matchedGeometryEffect(id: animMid, in: animation)
.scrollPosition(id: $detailScrollPosition)
.scrollTargetBehavior(.paging)
.scrollIndicators(.hidden)
.overlay(alignment: .topLeading) {
Button("", systemImage: "xmark.circle.fill") {
withAnimation(.easeInOut(duration: 0.15)){
isExpanded = false
}
}
.foregroundStyle(.white.opacity(0.8), .white.opacity(0.15))
.padding(.top, 100).padding(.leading)
}
}
}
}
}
}
// IGNORE
#Preview {
TopPostMediaView()
}
let mynewtemp: [tempStruct] = [tempStruct(id: UUID(), photo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRmCy16nhIbV3pI1qLYHMJKwbH2458oiC9EmA&s"), tempStruct(id: UUID(), photo: "https://th.bing.com/th/id/OIG2.9O4YqGf98tiYzjKDvg7L"), tempStruct(id: UUID(), photo: "https://th.bing.com/th/id/OIG2.9O4YqGf98tiYzjKDvg7L")]
struct tempStruct: Identifiable, Hashable {
var id: UUID
var photo: String
}
func viewHeight() -> CGFloat {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
return window?.screen.bounds.height ?? 0
}
使用选项卡视图效果更好,我还必须在底部视图中匹配的地理修改器之前添加框架修改器。
import SwiftUI
import Kingfisher
struct TopPostMediaView: View {
@State var isExpanded: Bool = false
@Namespace var animation
@State var detailScrollPosition: UUID? = nil
@State var animMid = ""
@State var offset: CGSize = .zero
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 10) {
GeometryReader {
let size = $0.size
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(mynewtemp) { pic in
LazyHStack {
let mid = pic.photo + pic.id.uuidString
if !isExpanded || animMid != mid {
KFImage(URL(string: pic.photo))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: size.width)
.overlay(content: {
RoundedRectangle(cornerRadius: 10).stroke(Color.gray.opacity(0.4), lineWidth: 1.0)
})
.clipShape(RoundedRectangle(cornerRadius: 10))
.contentShape(RoundedRectangle(cornerRadius: 10))
.frame(height: 250)
.matchedGeometryEffect(id: mid, in: animation)
.onTapGesture {
withAnimation(.easeInOut(duration: 0.15)){
detailScrollPosition = pic.id
}
animMid = mid
withAnimation(.easeInOut(duration: 0.15)){
isExpanded = true
}
}
} else {
KFImage(URL(string: pic.photo))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: size.width)
.clipShape(RoundedRectangle(cornerRadius: 10))
.contentShape(RoundedRectangle(cornerRadius: 10))
.opacity(0.0)
}
}
.frame(maxWidth: size.width)
.frame(height: size.height)
.contentShape(.rect)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $detailScrollPosition, anchor: .center)
.scrollIndicators(.hidden)
.scrollTargetBehavior(.viewAligned)
.scrollClipDisabled()
}.frame(height: 250)
}
}
.overlay {
if isExpanded {
ZStack {
Color.black.frame(height: viewHeight() + 30.0).clipShape(RoundedRectangle(cornerRadius: 40))
TabView(selection: $detailScrollPosition) {
ForEach(mynewtemp) { pic in
KFImage(URL(string: pic.photo))
.resizable()
.aspectRatio(contentMode: .fit)
//.frame(width: widthOrHeight(width: true))
.containerRelativeFrame(.horizontal)
.clipShape(Rectangle())
.contentShape(Rectangle())
.tag(pic.id)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.matchedGeometryEffect(id: animMid, in: animation)
.overlay(alignment: .topLeading) {
Button("", systemImage: "xmark.circle.fill") {
withAnimation(.easeInOut(duration: 0.15)){
isExpanded = false
}
}
.foregroundStyle(.white.opacity(0.8), .white.opacity(0.15)).font(.title)
.padding(.top, 100).padding(.leading)
}
}
.offset(x: offset.width, y: offset.height)
.gesture (
DragGesture()
.onChanged { value in
if value.translation.height >= 0 || offset != .zero {
offset = value.translation
}
}
.onEnded({ value in
withAnimation {
offset = .zero
}
if value.translation.height > 100 {
withAnimation(.easeInOut(duration: 0.15)){
isExpanded = false
}
}
})
)
}
}
}
}
#Preview {
TopPostMediaView()
}
let mynewtemp: [tempStruct] = [tempStruct(id: UUID(), photo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRmCy16nhIbV3pI1qLYHMJKwbH2458oiC9EmA&s"), tempStruct(id: UUID(), photo: "https://th.bing.com/th/id/OIG2.9O4YqGf98tiYzjKDvg7L"), tempStruct(id: UUID(), photo: "https://th.bing.com/th/id/OIG2.9O4YqGf98tiYzjKDvg7L")]
struct tempStruct: Identifiable, Hashable {
var id: UUID
var photo: String
}
func viewHeight() -> CGFloat {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
return window?.screen.bounds.height ?? 0
}