Scrollable and Zoomable Image
Если кратко, то я хочу добавить возможность увеличения изображения(как в галерее, нецентрированно и без возврата в начальное положение). Есть способ (struct Zoom), но нужно чтобы он создавался заново после каждой смены image и по возможности давал знать о своём состоянии.
Я хочу добавить в своё приложение возможность нецентрированно увеличивать изображение, как в галлерее и нашел этот способ(struct Zoom). Он идеален для меня, за исключением тех моментов, что после смены image все параметры, которые накладывались при увеличении на предыдущую image, сохраняются и в новой. Также не понимаю каким образом можно возвращать от туда какую-то информацию об увеличении, для того чтобы отталкиваясь от неё вносить какие-то изменения на общей странице(например, отлавливать DragGesture, если нет увеличения и запрещать в противном случае). Также я находит способ увеличение как в Instagram с помощью UIGestureRecognizer, но он мне не очень подходит, так как там есть возврат изображения в исходное состояние после того как пользователь поднимает пальцы с экрана.
import SwiftUI
struct mainContent: Hashable {
var image: UIImage = UIImage()
var test: String = String()
}
struct Page: View {
@Binding var main: Dictionary<Int, mainContent>
@State var blurMap: Bool = false
@State var image: UIImage = UIImage()
@State var zIndexValue: Bool = false
var body: some View {
ZStack {
ScrollView(.vertical, showsIndicators: false) {
Spacer()
.frame(height: UIScreen.main.bounds.height*0.08)
ForEach (Array(main.keys).sorted(by: {$0 < $1}), id: \.self) { m in
Picture(image: main[m]!.image, text: main[m]!.text)
.onTapGesture(count: 1) {
zIndexValue = true
image = main[m]!.image
blurMap = true
}
}
}
.disabled(zIndexValue)
.blur(radius: blurMap ? 15 : 0)
.zIndex(zIndexValue ? 0 : 1)
// MARK: - This picture i want to pinch zoom
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: UIScreen.main.bounds.height*0.25)
.zIndex(zIndexValue ? 1 : 0)
// MARK: - I did this, but OFFSET and SCALE are remembered when I open another picture
// Zoom {
// Image(uiImage: image)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(height: UIScreen.main.bounds.height*0.25)
// }
// .zIndex(zIndexValue ? 1 : 0)
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height, alignment: .center)
.onTapGesture(count: 1) {
zIndexValue = false
image = UIImage()
blurMap = false
}
}
}
struct Zoom<Content: View>: UIViewRepresentable {
private var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.maximumZoomScale = 4
scrollView.minimumZoomScale = 1
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.bouncesZoom = true
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
hostedView.backgroundColor = UIColor.clear
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content))
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
context.coordinator.hostingController.rootView = self.content
assert(context.coordinator.hostingController.view.superview == uiView)
}
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
init(hostingController: UIHostingController<Content>) {
self.hostingController = hostingController
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
}
}
struct Picture: View {
var image: UIImage
var text: String
var body: some View {
VStack {
ZoomableScrollView {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: UIScreen.main.bounds.height*0.25)
}
.frame(height: UIScreen.main.bounds.height*0.3)
HStack {
Firefly()
Text(text)
.foregroundColor(Color.white)
}
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height*0.35, alignment: .center)
.padding(.vertical, 5)
}
}
struct Firefly: View {
var body: some View {
Circle()
.fill(Color.blue)
.shadow(color: Color.white, radius: 2.5)
.frame(width: 5, height: 5)
}
}
Ответы (1 шт):
Пример с указанным вами поведением, используется Binding для двустороннего управления зумом (переменная zoomScale для вывода зума в основное вью и для сброса зума при тапе)
//
// ContentView.swift
// ZoomableImage
//
import SwiftUI
struct MainContent: Hashable { // MARK: fixed struct name case
var image: UIImage
var text: String
}
struct Page: View {
@Binding var main: Dictionary<Int, MainContent>
@State var blurMap: Bool = false
@State var image: UIImage = UIImage()
@State var zIndexValue: Bool = false
@State var zoomScale: Double
var body: some View {
ZStack {
ScrollView(.vertical, showsIndicators: false) {
Spacer()
.frame(height: UIScreen.main.bounds.height*0.08)
ForEach (Array(main.keys).sorted(by: {$0 < $1}), id: \.self) { m in
Picture(image: main[m]!.image, text: main[m]!.text)
.onTapGesture(count: 1) {
zIndexValue = true
image = main[m]!.image
blurMap = true
}
}
}
.disabled(zIndexValue)
.blur(radius: blurMap ? 15 : 0)
.zIndex(zIndexValue ? 0 : 1)
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: UIScreen.main.bounds.height*0.25)
.zIndex(zIndexValue ? 1 : 0)
Zoom(zoomScale: $zoomScale) {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: UIScreen.main.bounds.height*0.25)
}
.zIndex(zIndexValue ? 1 : 0)
// output zoom value for testing in bottom right screen corner
VStack {
Spacer()
HStack {
Spacer()
Text(String(zoomScale)).background(Color.white)
}
.padding(.all, 20)
}
.zIndex(1.0)
.padding(.all, 20)
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height, alignment: .center)
.onTapGesture(count: 1) {
zIndexValue = false
image = UIImage()
blurMap = false
zoomScale = 1.0 // reset zoom
}
}
}
struct Zoom<Content: View>: UIViewRepresentable {
@Binding var zoomScale: Double
private var content: Content
init(zoomScale: Binding<Double>, @ViewBuilder content: () -> Content) {
self._zoomScale = zoomScale
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.maximumZoomScale = 4
scrollView.minimumZoomScale = 1
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.bouncesZoom = true
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
hostedView.backgroundColor = UIColor.clear
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content), parent: self)
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
context.coordinator.hostingController.rootView = self.content
assert(context.coordinator.hostingController.view.superview == uiView)
// enable zoomScale reset, as a downside this is being set on each scrollViewDidZoom call
uiView.zoomScale = zoomScale
}
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
var parent: Zoom
init(hostingController: UIHostingController<Content>, parent: Zoom) {
self.hostingController = hostingController
self.parent = parent
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let zoomScale = scrollView.zoomScale
if zoomScale < 1.0 {
parent.zoomScale = 1.0
} else if zoomScale > 1.0 {
// fix for "Modifying state during view update, this will cause undefined behavior."
// if we reset zoomScale to 1.0
parent.zoomScale = zoomScale
}
}
}
}
struct Picture: View {
var image: UIImage
var text: String
var body: some View {
VStack {
Image(uiImage: image) // MARK: removed Zoom
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: UIScreen.main.bounds.height*0.25)
.frame(height: UIScreen.main.bounds.height*0.3)
HStack {
Firefly()
Text(text)
.foregroundColor(Color.black) // MARK: changed to black
}
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height*0.35, alignment: .center)
.padding(.vertical, 5)
}
}
struct Firefly: View {
var body: some View {
Circle()
.fill(Color.blue)
.shadow(color: Color.white, radius: 2.5)
.frame(width: 5, height: 5)
}
}