I have some view for zoomable images
struct ViewerFrameView: View {
@StateObject var viewModel: ViewerFrameViewModel
@State var image: UIImage = UIImage()
init(viewModel: ViewerFrameViewModel) {
self._viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
Group {
if viewModel.downloading {
LoaderView()
} else {
ZoomableScrollView(image: $image)
}
}
.onAppear {
DispatchQueue.global().async {
viewModel.getContent()
}
}
.onChange(of: viewModel.image, perform: { newValue in
image = newValue
print(newValue.size)
})
}
}
ZoomableScrollView is a UIViewRepresentable view that implement inside UIKit UIScrollView:
struct ZoomableScrollView: UIViewRepresentable {
@Binding var image: UIImage
var scrollView = ImageScrollView()
func makeUIView(context: Context) -> ImageScrollView {
return scrollView
}
func updateUIView(_ uiView: ImageScrollView, context: Context) {
scrollView.set(image: image)
}
}
class ImageScrollView: UIScrollView, UIScrollViewDelegate {
var imageZoomView: UIImageView!
lazy var zoomingTap: UITapGestureRecognizer = {
let zoomingTap = UITapGestureRecognizer(target: self, action: #selector(handleZoomingTap))
zoomingTap.numberOfTapsRequired = 2
return zoomingTap
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.delegate = self
self.showsVerticalScrollIndicator = false
self.showsHorizontalScrollIndicator = false
self.decelerationRate = UIScrollView.DecelerationRate.fast
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func set(image: UIImage) {
imageZoomView?.removeFromSuperview()
imageZoomView = nil
imageZoomView = UIImageView(image: image)
self.addSubview(imageZoomView)
configurateFor(imageSize: image.size)
}
func configurateFor(imageSize: CGSize) {
self.contentSize = imageSize
setCurrentMaxandMinZoomScale()
self.zoomScale = self.minimumZoomScale
self.imageZoomView.addGestureRecognizer(self.zoomingTap)
self.imageZoomView.isUserInteractionEnabled = true
}
override func layoutSubviews() {
super.layoutSubviews()
self.centerImage()
}
func setCurrentMaxandMinZoomScale() {
let boundsSize = self.bounds.size
let imageSize = imageZoomView.bounds.size
let xScale = boundsSize.width / imageSize.width
let yScale = boundsSize.height / imageSize.height
let minScale = min(xScale, yScale)
var maxScale: CGFloat = 1.0
if minScale < 0.1 {
maxScale = 0.3
}
if minScale >= 0.1 && minScale < 0.5 {
maxScale = 0.7
}
if minScale >= 0.5 {
maxScale = max(1.0, minScale)
}
self.minimumZoomScale = minScale
self.maximumZoomScale = maxScale
}
func centerImage() {
let boundsSize = self.bounds.size
var frameToCenter = imageZoomView.frame
if frameToCenter.size.width < boundsSize.width {
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2
} else {
frameToCenter.origin.x = 0
}
if frameToCenter.size.height < boundsSize.height {
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2
} else {
frameToCenter.origin.y = 0
}
imageZoomView.frame = frameToCenter
}
// gesture
@objc func handleZoomingTap(sender: UITapGestureRecognizer) {
let location = sender.location(in: sender.view)
self.zoom(point: location, animated: true)
}
func zoom(point: CGPoint, animated: Bool) {
let currectScale = self.zoomScale
let minScale = self.minimumZoomScale
let maxScale = self.maximumZoomScale
if minScale == maxScale && minScale > 1 {
return
}
let toScale = maxScale
let finalScale = (currectScale == minScale) ? toScale : minScale
let zoomRect = self.zoomRect(scale: finalScale, center: point)
self.zoom(to: zoomRect, animated: animated)
}
func zoomRect(scale: CGFloat, center: CGPoint) -> CGRect {
var zoomRect = CGRect.zero
let bounds = self.bounds
zoomRect.size.width = bounds.size.width / scale
zoomRect.size.height = bounds.size.height / scale
zoomRect.origin.x = center.x - (zoomRect.size.width / 2)
zoomRect.origin.y = center.y - (zoomRect.size.height / 2)
return zoomRect
}
// MARK: - UIScrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageZoomView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
self.centerImage()
}
}
but when loading complete it doesnt work, only empty screen and in console I have strange logs
2022-09-19 16:58:35.169270+0300 Storage[11768:301086] [Assert] -[UIScrollView _clampedZoomScale:allowRubberbanding:]: Must be called with non-zero scale 2022-09-19 16:58:35.169587+0300 Storage[11768:301086] [Unknown process name] CGAffineTransformInvert: singular matrix.