SwiftUI 匹配几何效果不适用于多个 ForEach
SwiftUI Matched Geometry Effect not working with multiple ForEach's
我基本上是在尝试重新创建照片应用程序。在这样做时,匹配的几何效果应该是在您单击 image/close 照片应用程序时重新创建照片应用程序中使用的动画的最佳方式。但是,在打开图像时它只执行一半的动画。关闭图像时,动画仅包含在 lazyvgrid 单个图像中,而不是整个视图。此外,画廊的第一张图片在关闭时根本没有动画。
图库视图由 lazyvgrid 和每个视图组成,全屏视图由 tabview 和每个视图组成。
这是它的样子:
主视图:
struct ImageSelectorView: View {
@EnvironmentObject var isvm: ImageSelectorViewModel
@Namespace var namespace
@State private var selectedImages: [SelectedImagesModel] = []
@State private var selectedImageID: String = ""
@State private var liveEventID: String = ""
@State var showImageFSV: Bool = false
@State var showPicker: Bool = false
@Binding var liveEvent: [EventModel]
public var pickerConfig: PHPickerConfiguration {
var config = PHPickerConfiguration(photoLibrary: .shared())
config.filter = .any(of: [.images, .livePhotos, .videos])
config.selectionLimit = 10
return config
}
private var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
private let viewWidth: CGFloat = UIScreen.main.bounds.width
private let viewHeight: CGFloat = UIScreen.main.bounds.height
private let viewHPadding: CGFloat = 30
init(liveEvent: Binding<[EventModel]>) {
self._liveEvent = liveEvent
}
var body: some View {
ZStack {
Color.theme.background.ignoresSafeArea()
VStack {
ZStack {
ScrollView(.vertical, showsIndicators: true) {
LazyVGrid(columns: self.gridItemLayout, alignment: .center, spacing: 0.5) {
ForEach(self.liveEvent[0].eventImages.indices) { image in
GalleryImage(selectedImageID: self.$selectedImageID, showImageFSV: self.$showImageFSV, image: self.liveEvent[0].eventImages[image], namespace: self.namespace)
}
}
}
if self.showImageFSV {
KFImagesFSV(eventImages: self.$liveEvent[0].eventImages, showImageFSV: self.$showImageFSV, selectedImageID: self.$selectedImageID, namespace: self.namespace)
}
}
}
}
}
}
图库图片视图:
struct GalleryImage: View {
@Binding var selectedImageID: String
@Binding var showImageFSV: Bool
public var image: EventImage
public var namespace: Namespace.ID
private let viewWidth: CGFloat = UIScreen.main.bounds.width
private let viewHeight: CGFloat = UIScreen.main.bounds.height
var body: some View {
Button {
DispatchQueue.main.async {
withAnimation(.spring()) {
self.selectedImageID = image.id
if self.selectedImageID == image.id {
self.showImageFSV.toggle()
}
}
}
} label: {
KFImage(URL(string: image.url))
.placeholder({
Image("topo")
.resizable()
.aspectRatio(contentMode: .fill)
})
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.fade(duration: 0.2)
.resizable()
.matchedGeometryEffect(id: self.selectedImageID == image.id ? "" : image.id, in: self.namespace)
.aspectRatio(contentMode: .fill)
.frame(width: (self.viewWidth/2.9) - 3, height: (self.viewWidth/2.9) - 3)
.clipped()
}
}
}
图片全屏视图(标签视图):
struct KFImagesFSV: View {
@Binding var eventImages: [EventImage]
@Binding var showImageFSV: Bool
@Binding var selectedImageID: String
public var namespace: Namespace.ID
private let viewWidth: CGFloat = UIScreen.main.bounds.width
private let viewHeight: CGFloat = UIScreen.main.bounds.height
private let viewHPadding: CGFloat = 30
var body: some View {
ZStack {
TabView(selection: self.$selectedImageID) {
ForEach(self.eventImages.indices) { image in
KFImage(URL(string: self.eventImages[image].url))
.placeholder({
Image("topo")
.resizable()
.aspectRatio(contentMode: .fill)
})
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.fade(duration: 0.2)
.resizable()
.tag(self.eventImages[image].id)
.matchedGeometryEffect(id: self.selectedImageID == self.eventImages[image].id ? self.eventImages[image].id : "", in: self.namespace)
.aspectRatio(contentMode: .fit)
.frame(width: self.viewWidth, height: self.viewHeight)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
}
}
}
这就是我的进展。从 FullScreenView 缩小到 GalleryView 有效。唯一没有的是 TabView 的干净放大。我想这是因为 TabView 的包装。
struct ImageStruct: Identifiable {
let id = UUID()
var image: String = ""
}
let imagesArray = [
ImageStruct(image: "image1"),
ImageStruct(image: "image2"),
ImageStruct(image: "image3"),
ImageStruct(image: "image4"),
ImageStruct(image: "image5"),
ImageStruct(image: "image6"),
ImageStruct(image: "image7"),
ImageStruct(image: "image8")
]
struct ContentView: View {
@Namespace var ns
@State private var selectedImage: UUID?
var body: some View {
// ZStack {
if selectedImage == nil {
GalleryView(selectedImage: $selectedImage, ns: ns)
} else {
FullScreenView(selectedImage: $selectedImage, ns: ns)
}
// }
}
}
struct GalleryView: View {
@Binding var selectedImage: UUID?
var ns: Namespace.ID
private let columns = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: true) {
LazyVGrid(columns: columns) {
ForEach(imagesArray) { image in
Color.clear.overlay(
Image(image.image)
.resizable()
.aspectRatio(contentMode: .fill)
.matchedGeometryEffect(id: image.id, in: ns, isSource: true)
)
.clipped()
.aspectRatio(1, contentMode: .fit)
.onTapGesture {
withAnimation {
selectedImage = image.id
}
}
}
}
}
}
}
}
struct FullScreenView: View {
@Binding var selectedImage: UUID?
var ns: Namespace.ID
init(selectedImage: Binding<UUID?>, ns: Namespace.ID) {
print(selectedImage)
self._selectedImage = selectedImage
self.ns = ns
// initialize selctedTab to selectedImage
self._selectedTab = State(initialValue: selectedImage.wrappedValue ?? UUID())
}
@State private var selectedTab: UUID
var body: some View {
TabView(selection: $selectedTab) {
ForEach(imagesArray) { image in
Image(image.image)
.resizable()
.scaledToFit()
// ternary applying effect only for selected tab
.matchedGeometryEffect(id: image.id == selectedTab ? selectedTab : UUID(),
in: ns, isSource: true)
.tag(image.id)
.onTapGesture {
withAnimation {
selectedImage = nil
}
}
}
}
.tabViewStyle(.page)
}
}
按照 ChrisR 的回答,我决定添加一个允许滑动关闭的手势。这对于 TabView 是不可能的,所以我用 LazyHstack 重新制作了整个东西。我不会将此标记为正确答案,因为原始问题不包含 lazyhstack。这只是解决原始问题的另一种方法。
Link 简化代码:
图库视图:
struct EventGalleryView: View {
@Namespace var namespace
@GestureState private var selectedImageOffset: CGSize = .zero
@Binding var eventImages: [EventImage]
@State private var selectedImageIndex: Int? = nil
@State private var selectedImageScale: CGFloat = 1
@State private var showFSV: Bool = false
@State private var isSwiping: Bool = false
@State private var isSelecting: Bool = false
private var gridItemLayout = Array(repeating: GridItem(.flexible()), count: 3)
init(eventImages: Binding<[EventImage]>) {
self._eventImages = eventImages
}
var body: some View {
GeometryReader { geo in
let geoWidth = geo.size.width
let geoHeight = geo.size.height
ScrollView(.vertical, showsIndicators: true) {
LazyVGrid(columns: self.gridItemLayout, alignment: .center, spacing: 0.5) {
ForEach(eventImages) { image in
GalleryImageView(image: image)
.matchedGeometryEffect(id: eventImages.firstIndex(of: image), in: self.namespace, isSource: self.showFSV ? false : true)
.aspectRatio(contentMode: .fill)
.frame(width: (geoWidth/2.9) - 3, height: (geoWidth/2.9) - 3, alignment: .center)
.clipped()
.contentShape(Rectangle())
.opacity(eventImages.firstIndex(of: image) == selectedImageIndex ? 0 : 1)
.onTapGesture {
DispatchQueue.main.async {
withAnimation(.spring()) {
self.showFSV = true
self.selectedImageIndex = eventImages.firstIndex(of: image)
}
}
}
}
}
}
.zIndex(0)
ImageFSV(selectedImageOffset: self.selectedImageOffset, showFSV: self.$showFSV, selectedImageIndex: self.$selectedImageIndex, selectedImageScale: self.$selectedImageScale, isSelecting: self.$isSelecting, isSwiping: self.$isSwiping, eventImages: self.eventImages, geoWidth: geoWidth, geoHeight: geoHeight, namespace: self.namespace)
}
}
}
FSV 图像视图:
struct ImageFSV: View {
@GestureState var selectedImageOffset: CGSize
@State private var backgroundOpacity: CGFloat = 1
@Binding var showFSV: Bool
@Binding var selectedImageIndex: Int?
@Binding var selectedImageScale: CGFloat
@Binding var isSelecting: Bool
@Binding var isSwiping: Bool
public var eventImages: [EventImage]
public let geoWidth: CGFloat
public let geoHeight: CGFloat
public let namespace: Namespace.ID
var body: some View {
if self.showFSV, let index = self.selectedImageIndex {
Color.theme.background.ignoresSafeArea()
.opacity(self.backgroundOpacity)
.zIndex(1)
LazyHStack(spacing: 0) {
ForEach(eventImages) { image in
GalleryImageView(image: image)
.if(self.eventImages.firstIndex(of: image) == self.selectedImageIndex && self.isSelecting, transform: { view in
view
.matchedGeometryEffect(id: self.selectedImageIndex, in: self.namespace, isSource: true)
})
.aspectRatio(contentMode: .fit)
.frame(width: geoWidth, height: geoHeight, alignment: .center)
.scaleEffect(eventImages.firstIndex(of: image) == self.selectedImageIndex ? self.selectedImageScale : 1)
.offset(x: -CGFloat(index) * geoWidth)
.offset(eventImages.firstIndex(of: image) == self.selectedImageIndex ? self.selectedImageOffset : .zero)
}
}
.animation(.easeOut(duration: 0.25), value: index)
.highPriorityGesture(
DragGesture()
.onChanged({ value in
DispatchQueue.main.async {
if !self.isSelecting && (value.translation.width > 5 || value.translation.width < -5) {
self.isSwiping = true
}
if !self.isSwiping && (value.translation.height > 5 || value.translation.height < -5) {
self.isSelecting = true
}
}
})
.updating(self.$selectedImageOffset, body: { value, state, _ in
if self.isSwiping {
state = CGSize(width: value.translation.width, height: 0)
} else if self.isSelecting {
state = CGSize(width: value.translation.width, height: value.translation.height)
}
})
.onEnded({ value in
DispatchQueue.main.async {
self.isSwiping = false
if value.translation.height > 150 && self.isSelecting {
withAnimation(.spring()) {
self.showFSV = false
self.selectedImageIndex = nil
self.isSelecting = false
}
} else {
self.isSelecting = false
let offset = value.translation.width / geoWidth*6
if offset > 0.5 && self.selectedImageIndex ?? 0 > 0 {
self.selectedImageIndex! -= 1
} else if offset < -0.5 && self.selectedImageIndex ?? 0 < (eventImages.count - 1) {
self.selectedImageIndex! += 1
}
}
}
})
)
.onChange(of: self.selectedImageOffset) { imageOffset in
DispatchQueue.main.async {
withAnimation(.easeIn) {
switch imageOffset.height {
case 50..<70:
self.backgroundOpacity = 0.8
case 70..<90:
self.backgroundOpacity = 0.6
case 90..<110:
self.backgroundOpacity = 0.4
case 110..<130:
self.backgroundOpacity = 0.2
case 130..<1000:
self.backgroundOpacity = 0.0
default:
self.backgroundOpacity = 1.0
}
}
let progress = imageOffset.height / geoHeight
if 1 - progress > 0.5 {
self.selectedImageScale = 1 - progress
}
}
}
.zIndex(2)
}
}
}
画廊图像视图:
struct GalleryImageView: View {
public let image: EventImage
var body: some View {
KFImage(URL(string: image.url))
.placeholder({
Image("topo")
.resizable()
.aspectRatio(contentMode: .fill)
})
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.fade(duration: 0.2)
.resizable()
}
}
EventImage 模型:
struct EventImage: Identifiable, Codable, Hashable {
var id = UUID().uuidString
var url: String
var voteCount: Int
private enum eventImage: String, CodingKey {
case id
case imageURL
case voteCount
}
}
我正在建立一个类似的画廊,我能够让它发挥作用。
您需要在同一视图中包含 LazyVGrid 和 TabView。
然后像这样应用 matchedGeometryEffect:
HStack {
TabView(...) {
...
}
}.matchedGeometryEffect(...)
编辑:刚刚测试过 - 您可以将它们放在单独的视图中,但您需要在带有 TabView 的视图上应用 matchedGeometryEffect,而不是在视图内部。
我基本上是在尝试重新创建照片应用程序。在这样做时,匹配的几何效果应该是在您单击 image/close 照片应用程序时重新创建照片应用程序中使用的动画的最佳方式。但是,在打开图像时它只执行一半的动画。关闭图像时,动画仅包含在 lazyvgrid 单个图像中,而不是整个视图。此外,画廊的第一张图片在关闭时根本没有动画。
图库视图由 lazyvgrid 和每个视图组成,全屏视图由 tabview 和每个视图组成。
这是它的样子:
主视图:
struct ImageSelectorView: View {
@EnvironmentObject var isvm: ImageSelectorViewModel
@Namespace var namespace
@State private var selectedImages: [SelectedImagesModel] = []
@State private var selectedImageID: String = ""
@State private var liveEventID: String = ""
@State var showImageFSV: Bool = false
@State var showPicker: Bool = false
@Binding var liveEvent: [EventModel]
public var pickerConfig: PHPickerConfiguration {
var config = PHPickerConfiguration(photoLibrary: .shared())
config.filter = .any(of: [.images, .livePhotos, .videos])
config.selectionLimit = 10
return config
}
private var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
private let viewWidth: CGFloat = UIScreen.main.bounds.width
private let viewHeight: CGFloat = UIScreen.main.bounds.height
private let viewHPadding: CGFloat = 30
init(liveEvent: Binding<[EventModel]>) {
self._liveEvent = liveEvent
}
var body: some View {
ZStack {
Color.theme.background.ignoresSafeArea()
VStack {
ZStack {
ScrollView(.vertical, showsIndicators: true) {
LazyVGrid(columns: self.gridItemLayout, alignment: .center, spacing: 0.5) {
ForEach(self.liveEvent[0].eventImages.indices) { image in
GalleryImage(selectedImageID: self.$selectedImageID, showImageFSV: self.$showImageFSV, image: self.liveEvent[0].eventImages[image], namespace: self.namespace)
}
}
}
if self.showImageFSV {
KFImagesFSV(eventImages: self.$liveEvent[0].eventImages, showImageFSV: self.$showImageFSV, selectedImageID: self.$selectedImageID, namespace: self.namespace)
}
}
}
}
}
}
图库图片视图:
struct GalleryImage: View {
@Binding var selectedImageID: String
@Binding var showImageFSV: Bool
public var image: EventImage
public var namespace: Namespace.ID
private let viewWidth: CGFloat = UIScreen.main.bounds.width
private let viewHeight: CGFloat = UIScreen.main.bounds.height
var body: some View {
Button {
DispatchQueue.main.async {
withAnimation(.spring()) {
self.selectedImageID = image.id
if self.selectedImageID == image.id {
self.showImageFSV.toggle()
}
}
}
} label: {
KFImage(URL(string: image.url))
.placeholder({
Image("topo")
.resizable()
.aspectRatio(contentMode: .fill)
})
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.fade(duration: 0.2)
.resizable()
.matchedGeometryEffect(id: self.selectedImageID == image.id ? "" : image.id, in: self.namespace)
.aspectRatio(contentMode: .fill)
.frame(width: (self.viewWidth/2.9) - 3, height: (self.viewWidth/2.9) - 3)
.clipped()
}
}
}
图片全屏视图(标签视图):
struct KFImagesFSV: View {
@Binding var eventImages: [EventImage]
@Binding var showImageFSV: Bool
@Binding var selectedImageID: String
public var namespace: Namespace.ID
private let viewWidth: CGFloat = UIScreen.main.bounds.width
private let viewHeight: CGFloat = UIScreen.main.bounds.height
private let viewHPadding: CGFloat = 30
var body: some View {
ZStack {
TabView(selection: self.$selectedImageID) {
ForEach(self.eventImages.indices) { image in
KFImage(URL(string: self.eventImages[image].url))
.placeholder({
Image("topo")
.resizable()
.aspectRatio(contentMode: .fill)
})
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.fade(duration: 0.2)
.resizable()
.tag(self.eventImages[image].id)
.matchedGeometryEffect(id: self.selectedImageID == self.eventImages[image].id ? self.eventImages[image].id : "", in: self.namespace)
.aspectRatio(contentMode: .fit)
.frame(width: self.viewWidth, height: self.viewHeight)
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
}
}
}
这就是我的进展。从 FullScreenView 缩小到 GalleryView 有效。唯一没有的是 TabView 的干净放大。我想这是因为 TabView 的包装。
struct ImageStruct: Identifiable {
let id = UUID()
var image: String = ""
}
let imagesArray = [
ImageStruct(image: "image1"),
ImageStruct(image: "image2"),
ImageStruct(image: "image3"),
ImageStruct(image: "image4"),
ImageStruct(image: "image5"),
ImageStruct(image: "image6"),
ImageStruct(image: "image7"),
ImageStruct(image: "image8")
]
struct ContentView: View {
@Namespace var ns
@State private var selectedImage: UUID?
var body: some View {
// ZStack {
if selectedImage == nil {
GalleryView(selectedImage: $selectedImage, ns: ns)
} else {
FullScreenView(selectedImage: $selectedImage, ns: ns)
}
// }
}
}
struct GalleryView: View {
@Binding var selectedImage: UUID?
var ns: Namespace.ID
private let columns = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: true) {
LazyVGrid(columns: columns) {
ForEach(imagesArray) { image in
Color.clear.overlay(
Image(image.image)
.resizable()
.aspectRatio(contentMode: .fill)
.matchedGeometryEffect(id: image.id, in: ns, isSource: true)
)
.clipped()
.aspectRatio(1, contentMode: .fit)
.onTapGesture {
withAnimation {
selectedImage = image.id
}
}
}
}
}
}
}
}
struct FullScreenView: View {
@Binding var selectedImage: UUID?
var ns: Namespace.ID
init(selectedImage: Binding<UUID?>, ns: Namespace.ID) {
print(selectedImage)
self._selectedImage = selectedImage
self.ns = ns
// initialize selctedTab to selectedImage
self._selectedTab = State(initialValue: selectedImage.wrappedValue ?? UUID())
}
@State private var selectedTab: UUID
var body: some View {
TabView(selection: $selectedTab) {
ForEach(imagesArray) { image in
Image(image.image)
.resizable()
.scaledToFit()
// ternary applying effect only for selected tab
.matchedGeometryEffect(id: image.id == selectedTab ? selectedTab : UUID(),
in: ns, isSource: true)
.tag(image.id)
.onTapGesture {
withAnimation {
selectedImage = nil
}
}
}
}
.tabViewStyle(.page)
}
}
按照 ChrisR 的回答,我决定添加一个允许滑动关闭的手势。这对于 TabView 是不可能的,所以我用 LazyHstack 重新制作了整个东西。我不会将此标记为正确答案,因为原始问题不包含 lazyhstack。这只是解决原始问题的另一种方法。
Link 简化代码:
图库视图:
struct EventGalleryView: View {
@Namespace var namespace
@GestureState private var selectedImageOffset: CGSize = .zero
@Binding var eventImages: [EventImage]
@State private var selectedImageIndex: Int? = nil
@State private var selectedImageScale: CGFloat = 1
@State private var showFSV: Bool = false
@State private var isSwiping: Bool = false
@State private var isSelecting: Bool = false
private var gridItemLayout = Array(repeating: GridItem(.flexible()), count: 3)
init(eventImages: Binding<[EventImage]>) {
self._eventImages = eventImages
}
var body: some View {
GeometryReader { geo in
let geoWidth = geo.size.width
let geoHeight = geo.size.height
ScrollView(.vertical, showsIndicators: true) {
LazyVGrid(columns: self.gridItemLayout, alignment: .center, spacing: 0.5) {
ForEach(eventImages) { image in
GalleryImageView(image: image)
.matchedGeometryEffect(id: eventImages.firstIndex(of: image), in: self.namespace, isSource: self.showFSV ? false : true)
.aspectRatio(contentMode: .fill)
.frame(width: (geoWidth/2.9) - 3, height: (geoWidth/2.9) - 3, alignment: .center)
.clipped()
.contentShape(Rectangle())
.opacity(eventImages.firstIndex(of: image) == selectedImageIndex ? 0 : 1)
.onTapGesture {
DispatchQueue.main.async {
withAnimation(.spring()) {
self.showFSV = true
self.selectedImageIndex = eventImages.firstIndex(of: image)
}
}
}
}
}
}
.zIndex(0)
ImageFSV(selectedImageOffset: self.selectedImageOffset, showFSV: self.$showFSV, selectedImageIndex: self.$selectedImageIndex, selectedImageScale: self.$selectedImageScale, isSelecting: self.$isSelecting, isSwiping: self.$isSwiping, eventImages: self.eventImages, geoWidth: geoWidth, geoHeight: geoHeight, namespace: self.namespace)
}
}
}
FSV 图像视图:
struct ImageFSV: View {
@GestureState var selectedImageOffset: CGSize
@State private var backgroundOpacity: CGFloat = 1
@Binding var showFSV: Bool
@Binding var selectedImageIndex: Int?
@Binding var selectedImageScale: CGFloat
@Binding var isSelecting: Bool
@Binding var isSwiping: Bool
public var eventImages: [EventImage]
public let geoWidth: CGFloat
public let geoHeight: CGFloat
public let namespace: Namespace.ID
var body: some View {
if self.showFSV, let index = self.selectedImageIndex {
Color.theme.background.ignoresSafeArea()
.opacity(self.backgroundOpacity)
.zIndex(1)
LazyHStack(spacing: 0) {
ForEach(eventImages) { image in
GalleryImageView(image: image)
.if(self.eventImages.firstIndex(of: image) == self.selectedImageIndex && self.isSelecting, transform: { view in
view
.matchedGeometryEffect(id: self.selectedImageIndex, in: self.namespace, isSource: true)
})
.aspectRatio(contentMode: .fit)
.frame(width: geoWidth, height: geoHeight, alignment: .center)
.scaleEffect(eventImages.firstIndex(of: image) == self.selectedImageIndex ? self.selectedImageScale : 1)
.offset(x: -CGFloat(index) * geoWidth)
.offset(eventImages.firstIndex(of: image) == self.selectedImageIndex ? self.selectedImageOffset : .zero)
}
}
.animation(.easeOut(duration: 0.25), value: index)
.highPriorityGesture(
DragGesture()
.onChanged({ value in
DispatchQueue.main.async {
if !self.isSelecting && (value.translation.width > 5 || value.translation.width < -5) {
self.isSwiping = true
}
if !self.isSwiping && (value.translation.height > 5 || value.translation.height < -5) {
self.isSelecting = true
}
}
})
.updating(self.$selectedImageOffset, body: { value, state, _ in
if self.isSwiping {
state = CGSize(width: value.translation.width, height: 0)
} else if self.isSelecting {
state = CGSize(width: value.translation.width, height: value.translation.height)
}
})
.onEnded({ value in
DispatchQueue.main.async {
self.isSwiping = false
if value.translation.height > 150 && self.isSelecting {
withAnimation(.spring()) {
self.showFSV = false
self.selectedImageIndex = nil
self.isSelecting = false
}
} else {
self.isSelecting = false
let offset = value.translation.width / geoWidth*6
if offset > 0.5 && self.selectedImageIndex ?? 0 > 0 {
self.selectedImageIndex! -= 1
} else if offset < -0.5 && self.selectedImageIndex ?? 0 < (eventImages.count - 1) {
self.selectedImageIndex! += 1
}
}
}
})
)
.onChange(of: self.selectedImageOffset) { imageOffset in
DispatchQueue.main.async {
withAnimation(.easeIn) {
switch imageOffset.height {
case 50..<70:
self.backgroundOpacity = 0.8
case 70..<90:
self.backgroundOpacity = 0.6
case 90..<110:
self.backgroundOpacity = 0.4
case 110..<130:
self.backgroundOpacity = 0.2
case 130..<1000:
self.backgroundOpacity = 0.0
default:
self.backgroundOpacity = 1.0
}
}
let progress = imageOffset.height / geoHeight
if 1 - progress > 0.5 {
self.selectedImageScale = 1 - progress
}
}
}
.zIndex(2)
}
}
}
画廊图像视图:
struct GalleryImageView: View {
public let image: EventImage
var body: some View {
KFImage(URL(string: image.url))
.placeholder({
Image("topo")
.resizable()
.aspectRatio(contentMode: .fill)
})
.loadDiskFileSynchronously()
.cacheMemoryOnly()
.fade(duration: 0.2)
.resizable()
}
}
EventImage 模型:
struct EventImage: Identifiable, Codable, Hashable {
var id = UUID().uuidString
var url: String
var voteCount: Int
private enum eventImage: String, CodingKey {
case id
case imageURL
case voteCount
}
}
我正在建立一个类似的画廊,我能够让它发挥作用。 您需要在同一视图中包含 LazyVGrid 和 TabView。
然后像这样应用 matchedGeometryEffect:
HStack {
TabView(...) {
...
}
}.matchedGeometryEffect(...)
编辑:刚刚测试过 - 您可以将它们放在单独的视图中,但您需要在带有 TabView 的视图上应用 matchedGeometryEffect,而不是在视图内部。