首页 > 解决方案 > 为图像缩放添加 UIScrollView 会破坏 UIImageView 中的绘图

问题描述

我使用 2 个 UIImageViews 创建了一个绘图画布。有 2 个的原因与橡皮擦功能(在此最小可重现示例中不存在)有关,该功能将白色应用于其中一个,当用户将手指从屏幕上抬起时,它将白色笔划合并到另一个 UIImageView 中。

当我添加一个 UIScrollView 以允许通过捏合和双击手势进行放大和缩小时,它阻止了我绘图。touchesBegan 方法中的 print 语句永远不会执行。关于 UIScrollView 的某些东西干扰了绘图方法。

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
    }
}

标签: iosswiftuiscrollviewuiimageviewuikit

解决方案


推荐阅读