요즘 커머스 관련한 앱을 보다보면, 이미지를 확대해서 보는 기능이 있다
SwiftUI 상에 있는 MagnificationGesture 를 이용해 구현 했을려 했으나, 필자가 원하는 방식대로 적용하기 어려워
ViewController 의 힘을 빌렸다. ViewPager 상에서 Zoom 을 이용해 확대 기능 + 더블탭을 이용해 다시 원상태로 되돌릴수 있도록 만들었다.
1. ImageZoomRepresentable
import Foundation
import SwiftUI
import Combine
struct ImageZoomRepresentable<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> ImageZoomViewController {
let vc = ImageZoomViewController()
vc.hostingController.rootView = AnyView(self.content())
return vc
}
func updateUIViewController(_ viewController: ImageZoomViewController, context: Context) { }
}
class ImageZoomViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let v = UIScrollView()
v.isPagingEnabled = true
return v
}()
var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))
var zoomScale: CGFloat = 0 {
didSet {
if zoomScale > 1.0 {
scrollView.isScrollEnabled = true
} else {
scrollView.isScrollEnabled = false
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.parentConstraint(of: self.scrollView, to: self.view) // scrollview -> view
self.hostingController.willMove(toParent: self)
self.scrollView.addSubview(self.hostingController.view)
self.childConstraint(of: self.hostingController.view, to: self.scrollView, isZoom: false) // image -> scrollview
self.hostingController.didMove(toParent: self)
scrollView.maximumZoomScale = 2.5
scrollView.minimumZoomScale = 1
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(onDoubleTap(gestureRecognizer:)))
tapRecognizer.numberOfTapsRequired = 2
scrollView.addGestureRecognizer(tapRecognizer)
scrollView.delegate = self
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { [self] in
initScrollContent()
}
}
//MARK: - View Constraint
//가장 부모 뷰와 스크롤뷰에 대한 위치 제약조건 (ScrollView)
func parentConstraint(of viewA: UIView, to viewB: UIView) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor)
])
}
//스크롤뷰와 자식 뷰에 대한 위치 제약조건 (Content (Image))
func childConstraint(of viewA: UIView, to viewB: UIView, isZoom: Bool) {
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.addConstraints([
viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
viewA.heightAnchor.constraint(equalToConstant: 270)
])
}
// 더블 클릭했을때 이미지 원복
@objc func onDoubleTap(gestureRecognizer: UITapGestureRecognizer) {
initScrollContent()
}
// init 시에 부모 뷰와 scrollview content 를 정렬
func initScrollContent() {
scrollView.setZoomScale(1.0, animated: true)
zoomScale = 1.0
let offsetX = max((self.view.bounds.size.width - self.hostingController.view.frame.width) * 0.5, 0.0)
let offsetY = max((self.view.bounds.size.height - self.hostingController.view.frame.height) * 0.5, 0.0)
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: offsetY, right: offsetX);
}
}
extension ImageZoomViewController: UIScrollViewDelegate {
//MARK: - ScrollView Zoom On
// 줌 하고자 하는 뷰를 리턴한다.
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.hostingController.view
}
//MARK: - ScrollView Zoom 최대치, 최소치 컨트롤
// 스크롤 할때마다 계속 호출하여 현재 줌 스케일 값을 할당한다.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
zoomScale = scrollView.zoomScale
}
//MARK: - ScrollView Zoom End
// scrollview Zoom 완료시에 scrollview content를 중간으로 정렬함
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
let offsetX = max((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0)
let offsetY = max((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0)
scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: offsetY, right: offsetX);
}
}
상세한 설명은 주석처리 했으나, 간략하게 정리하자면
실제 줌을 하여도 스크롤을 이용해서 내용을 확인 해야하기 때문에, 바깥은 ScrollView 로 구성하고, 확대 하고자 하는 뷰를 추가한다.
줌관련한 옵션을 추가하고 Delegate 를 이용하여 해당 함수 내용에 맞게 조정한다.
2. ImageZoomRepresentable
VStack {
ImageZoomRepresentable {
Image("image")
.resizable()
.frame(width: UIScreen.main.bounds.width ,height: 270)
}
}
적용은 정말 간단하다.
3. 적용
마치며
MagnificationGesture 를 이용해 완벽히 구현하진 못했지만, 많은 블로그를 참고해 만들어 결과가 나와서 다행이다.
ViewPager 를 이용해 다중 이미지를 보여주거나, 현재 이미지 인덱스를 보여줄수 있는 뷰를 추가하며 더욱더 완성도를 높힐수 있을것으로 보인다.
참고
https://stackoverflow.com/questions/9008975/how-to-tap-to-zoom-and-double-tap-to-zoom-out-in-ios
https://gist.github.com/timothycosta/0d8f64afeca0b6cc29665d87de0d94d2
https://github.com/1992Shubham/ImageViewer/blob/master/ImageViewer/ViewController.swift
https://stackoverflow.com/questions/1062693/how-to-disable-horizontal-scrolling-of-uiscrollview
'IOS' 카테고리의 다른 글
Swift - Google Login (0) | 2023.06.24 |
---|---|
SwiftUI - 프로젝트에 TCA 를 적용 해보자 (1) (0) | 2023.03.18 |
iOS - 프로젝트에 SPM 을 달아보자 (0) | 2023.02.19 |
Xcode - Google Sheet 에서 다국어 내용 가져오기 (0) | 2023.01.23 |
Swift - async await (0) | 2022.10.26 |