0

I created a drawing canvas using 2 UIImageViews. The reason there's 2 is to do with an eraser feature (not present in this minimum reproducible example), which applies a white colour to one of them, when user lifts finger off screen it merges the white strokes into the other UIImageView.

When I added a UIScrollView to allow zooming in and out with pinch and double tap gestures, it prevented me from drawing. The print statement in the touchesBegan method never executes. Something about the UIScrollView is interfering with the drawing methods.

import UIKit

extension UIView {

    var safeAreaBottom: CGFloat {
        if #available(iOS 11, *) {
            if let window = UIApplication.shared.keyWindowInConnectedScenes {
                return window.safeAreaInsets.bottom
            }
         }
     return 0
    }

    var safeAreaTop: CGFloat {
         if #available(iOS 11, *) {
            if let window = UIApplication.shared.keyWindowInConnectedScenes {
                return window.safeAreaInsets.top
            }
         }
     return 0
    }
}

/* Identifies the key window in the app so the current window can be accessed in above 2 methods to get it's bottom and top
   safe area insets (navigation toolbar / tabBar).
 */

extension UIApplication {
    var keyWindowInConnectedScenes: UIWindow? {
        return windows.first(where: { $0.isKeyWindow })
    }
}

extension ViewController: UIScrollViewDelegate {
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return finalImage
    }

    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        centerImage()
    }
}

class ViewController: UIViewController {

    @IBOutlet weak var imageScrollView: UIScrollView!
    @IBOutlet weak var finalImage: UIImageView!
    @IBOutlet weak var drawImage: UIImageView!
    
    var lastPoint = CGPoint.zero
    var color: UIColor = .black
    var lineWidth: CGFloat = 10.0
    var opacity: CGFloat = 1.0
    var swiped = Bool()
    var screenBounds = CGRect()
    var screenWidth = CGFloat()
    var screenHeight = CGFloat()
    let drawView = UIView()
    var image: UIImage?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        screenWidth = view.bounds.width
        screenHeight = view.bounds.height
        screenBounds = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
        drawView.alignmentRect(forFrame: screenBounds)
        
        image = UIImage(named: "landscape")
        finalImage.image = image
        finalImage.image = imageResize(image: finalImage.image!, sizeChange: finalImage.intrinsicContentSize)
        finalImage.contentMode = .scaleAspectFit
        finalImage.alignmentRect(forFrame: CGRect(origin: .zero, size: image?.size ?? CGSize(width: 0, height: 0)))
        drawImage.alignmentRect(forFrame: CGRect(origin: .zero, size: image?.size ?? CGSize(width: 0, height: 0)))
        
        imageScrollView.delegate = self
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
        if let image = image {
            setMinZoomScaleForImageSize(image.size)
        }
    }
    
    func imageResize(image: UIImage, sizeChange: CGSize) -> UIImage {

        let hasAlpha = true
        let scale: CGFloat = 0.0 // Use scale factor of main screen

        // Create a Drawing Environment (which will render to a bitmap image, later)
        UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)

        image.draw(in: CGRect(origin: .zero, size: sizeChange))

        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()

        // Clean up the Drawing Environment (created above)
        UIGraphicsEndImageContext()

        return scaledImage!
    }
    
    func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
        UIGraphicsBeginImageContext(CGSize(width: screenWidth, height: screenHeight))
        guard let context = UIGraphicsGetCurrentContext() else { return }
        finalImage.image?.draw(in: screenBounds)
      
        context.move(to: fromPoint)
        context.addLine(to: toPoint)
      
        context.setLineCap(.round)
        context.setBlendMode(.normal)
        context.setLineWidth(lineWidth)
        context.setStrokeColor(color.cgColor)
      
        context.strokePath()
      
        finalImage.image = UIGraphicsGetImageFromCurrentImageContext()
        finalImage.alpha = opacity
              
        UIGraphicsEndImageContext()
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("in touches began!")
        guard let touch = touches.first else { return }
        swiped = false
        lastPoint = touch.location(in: drawView)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        swiped = true
        var currentPoint = touch.location(in: drawView)
        drawLine(from: lastPoint, to: currentPoint)
        
        lastPoint = currentPoint
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        // draw a single point
        if swiped { drawLine(from: lastPoint, to: lastPoint) } else { return }
      
        // Merge drawImageView into finalImageView
        UIGraphicsBeginImageContext(finalImage.frame.size)
        finalImage.image?.draw(in: screenBounds, blendMode: .normal, alpha: 1.0)
        drawImage?.image?.draw(in: screenBounds, blendMode: .normal, alpha: opacity)
        finalImage.image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        drawImage.image = nil
    }
    
        
    @IBAction func doubleTapImage(_ sender: UITapGestureRecognizer) {
        if imageScrollView.zoomScale == imageScrollView.minimumZoomScale {
            imageScrollView.zoom(to: zoomRectangle(scale: imageScrollView.maximumZoomScale, center: sender.location(in: sender.view)), animated: true)
        } else {
            imageScrollView.setZoomScale(imageScrollView.minimumZoomScale, animated: true)
        }
    }
    
    // MARK: - Zoom IN / OUT Helper Methods Begin
    
    private func setMinZoomScaleForImageSize(_ imageSize: CGSize) {
        let widthScale = view.frame.width / imageSize.width
        let heightScale = view.frame.height / imageSize.height
        let minScale = min(widthScale, heightScale)

        // Scale the image down to fit in the view
        imageScrollView.minimumZoomScale = minScale
        imageScrollView.zoomScale = minScale

        // Set the image frame size after scaling down
        let imageWidth = imageSize.width * minScale
        let imageHeight = imageSize.height * minScale
        let newImageFrame = CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight)
        finalImage.frame = newImageFrame

        centerImage()
    }

    private func centerImage() {
        let imageViewSize = finalImage.frame.size
        let scrollViewSize = view.frame.size
        let verticalPadding = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0
        let horizontalPadding = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0

        imageScrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
    }

    private func zoomRectangle(scale: CGFloat, center: CGPoint) -> CGRect {
        var zoomRect = CGRect.zero
        zoomRect.size.height = finalImage.frame.size.height / scale
        zoomRect.size.width  = finalImage.frame.size.width  / scale
        zoomRect.origin.x = center.x - (center.x * imageScrollView.zoomScale)
        zoomRect.origin.y = center.y - (center.y * imageScrollView.zoomScale)

        return zoomRect
    }
}

Tirna
  • 117
  • 8
  • Take a look at this question/answer. Sounds like it is the same thing you're trying to do: https://stackoverflow.com/a/29800221/6257435 – DonMag Oct 29 '21 at 12:44

0 Answers0