SwiftUI — Как рисовать поверх изображения, а затем сохранять рисунок и изображение вместе как одно

#ios #swiftui

Вопрос:

У меня есть приложение, которое делает снимок представления, а затем снимок используется в качестве изображения, которое пользователь может нарисовать поверх. Я следовал этому руководству, чтобы получить функциональность рисования.

Все работает отлично, однако, если я попытаюсь сделать снимок изображения с рисунком сверху, оно сохранит его в фотоальбоме как просто изображение (без рисунка). Я хочу использовать кнопку для сохранения снимка изображения с недавно добавленным рисунком. Как видно из кода, я попытался использовать ZStack в представлении, которое я снимаю, однако у меня ничего не вышло.

Я думаю, что мне нужно что-то обновить в окне обновления, но я не уверен, что именно. Приведенный ниже минимальный воспроизводимый пример будет построен с учетом этой проблемы. Я ценю любые идеи.

Спасибо!

Просмотр содержимого

 import SwiftUI

struct ContentView: View {
    
    @State private var snapshotImage: UIImage? = nil
    
    var imageView: some View {
        Image("Test")
        //EmptyView() <- Uncomment and comment out the line above to just use an empty View as an image
    }

    var imageDrawingView: some View {
        ZStack {
            if let image = snapshotImage {
                Image(uiImage: image)
                CanvasViewWrapper()
            }
            
        }
    }
    
    var body: some View {
        if snapshotImage != nil {
            VStack {
                ZStack {
                    imageDrawingView
                }
                Button("Save Image") {
                    let imageWithDrawing = imageDrawingView.snapshot()
                    UIImageWriteToSavedPhotosAlbum(imageWithDrawing, nil, nil, nil) // Save to photos
                }
            }
        } else {
            VStack {
                imageView
                Button("Take ScreenShot") {
                    self.snapshotImage = imageView.snapshot()
                }
            }
        }
    }
}



extension View {
    func snapshot() -> UIImage {
        let controller = UIHostingController(rootView: self)
        let view = controller.view
        
        let targetSize = controller.view.intrinsicContentSize
        view?.bounds = CGRect(origin: .zero, size: targetSize)
        view?.backgroundColor = .clear
        
        let renderer = UIGraphicsImageRenderer(size: targetSize)
        
        return renderer.image { _ in
            view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
        }
    }
}

 

CanvasView

 import Foundation
import UIKit

class CanvasView: UIView {
    
    override func draw(_ rect: CGRect){
        super.draw(rect)
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        paths.forEach { path in
            switch(path.type) {
            case .move:
                context.move(to: path.point)
                break
            case .line:
                context.addLine(to: path.point)
                break
            }
        }
        
        context.setLineWidth(10)
        context.setLineCap(.round)
        context.strokePath()
    }
    
    @Published var paths = [Path]()
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let point = touches.first?.location(in: self) else { return }
        paths.append(Path(type: .move, point: point))
        setNeedsDisplay()
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let point = touches.first?.location(in: self) else { return }
        paths.append(Path(type: .line, point: point))
        setNeedsDisplay()
    }
}

struct Path {
    let type: PathType
    let point: CGPoint
    
    init(type: PathType, point: CGPoint) {
        self.type = type
        self.point = point
    }
    
    enum PathType {
        case move
        case line
    }
}
 

Оберточная бумага CanvasViewWrapper

 import SwiftUI

struct CanvasViewWrapper : UIViewRepresentable {
    func makeUIView(context: Context) -> UIView {
        return CanvasView()
    }
        func updateUIView(_ uiView: UIView, context: Context) {
        uiView.backgroundColor = .clear
    }
}
 

Апп

 import SwiftUI

@main
struct StackMinimalApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
 

Ответ №1:

Сделайте снимок UIImageView или UIView с помощью этого расширения

 extension UIView {
    func asImage() -> UIImage {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        } else {
            UIGraphicsBeginImageContext(self.frame.size)
            self.layer.render(in:UIGraphicsGetCurrentContext()!)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return UIImage(cgImage: image!.cgImage!)
        }
    }
    func asImages() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}
 

Воспользуйся

 let image = someView.asImage()
 

он вернет вам UIView с рисунком в виде изображения, а затем сохранит это изображение в вашей библиотеке.

сохранить

 func saveImage(image: UIImage) -> Bool {
    guard let data = UIImageJPEGRepresentation(image, 1) ?? UIImagePNGRepresentation(image) else {
        return false
    }
    guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
        return false
    }
    do {
        try data.write(to: directory.appendingPathComponent("fileName.png")!)
        return true
    } catch {   
        print(error.localizedDescription)
        return false
    }
}
 

использовать

 let success = saveImage(image: UIImage(named: "image.png")!)
 

Получите Изображение

 func getSavedImage(named: String) -> UIImage? {
    if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
        return UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(named).path)
    }
    return nil
}
 

использовать

 if let image = getSavedImage(named: "fileName") {
    // do something with image
}