ImageRenderer в SwiftUI

26 апреля 2023

*Renderer (рендерер)- визуализатор; программа, выполняющая рендеринг изображения.

Я работаю над медицинским приложением, в котором пользователю необходимо экспортировать данные о здоровье, “отрисованные”(*визуализированные) с помощью фреймворка Swift Charts. Это было вполне легко сделать, используя мощь нового типа ImageRenderer. На этой неделе мы узнаем, как использовать тип ImageRenderer для экспорта SwiftUI-вью в качестве изображения или PDF.

Тип ImageRenderer предоставляет простой в использовании API, позволяющий нам экспортировать иерархию вью SwiftUI в виде изображения. Давайте рассмотрим быстрый пример.

import SwiftUI
import Charts

struct MyChartView: View {
    let numbers: [Double]
    
    var body: some View {
        Chart {
            ForEach(Array(numbers.enumerated()), id: \.offset) { index, value in
                LineMark(
                    x: .value("Index", index),
                    y: .value("Value", value)
                )
            }
        }
    }
}

struct ContentView: View {
    @StateObject private var renderer = ImageRenderer(
        content: MyChartView(
            numbers: [1,2,3]
        )
    )
    
    var body: some View {
        if let image = renderer.uiImage {
            Image(uiImage: image)
        }
    }
}

 

Как вы можете видеть в приведенном выше примере, мы используем обертку свойства StateObject для определения экземпляра типа ImageRenderer. Тип ImageRenderer соответствует протоколу ObservableObject, и SwiftUI автоматически обновляет иерархию вью, когда бы экземпляр ImageRenderer, отмеченный оберткой свойства StateObject, не изменился.

Единственный параметр, который нам нужен для создания экземпляра типа ImageRenderer, - это content, и им должен быть SwiftUI-вью. Тип ImageRenderer предоставляет нам свойства uiImage и cgImage, позволяющие обратиться к отрисованному изображению контент-вью.

В предыдущем примере мы создаем экземпляр типа ImageRenderer, передавая экземпляр типа MyChartView, который использует фреймворк Swift Charts для отображения своего содержимого. В теле ContentView мы размещаем вью Image, чтобы показать отрисованную версию MyChartView. Как только рендерер готов, SwiftUI обновляет тело ContentView.

Это был статический пример использования типа ImageRenderer. Обычно нам нужно экспортировать динамические вью SwiftUI с данными, доступными только во время работы приложения.

struct SummaryContainerView: View {
    @State private var image: UIImage?
    @State private var shareSheetShown = false
    
    var body: some View {
        List {
            summarySection
        }
        .toolbar {
            Button("Export") {
                let renderer = ImageRenderer(content: summarySection)
                image = renderer.uiImage
                shareSheetShown = true
            }
        }
        .sheet(isPresented: $shareSheetShown) {
            if let image = image {
                Image(uiImage: image)
            }
        }
    }
    
    private var summarySection: some View {
        Section {
            // ...
        }
    }
}


Как вы можете видеть в приведенном выше примере, мы рендерим часть определенного SwiftUI-вью в изображение, выводящее на экран краткое описание некоторых данных. Тип ImageRenderer позволяет настраивать несколько параметров, влияющих на конечный результат экспортированного изображения. Например, мы можем изменить масштаб, размер и цветовой режим, используя свойства scale, proposedSize и colorMode.

struct SummaryContainerView: View {
    @State private var image: UIImage?
    @State private var shareSheetShown = false
    @Environment(\.displayScale) private var scale
    
    var body: some View {
        List {
            summarySection
        }
        .toolbar {
            Button("Export") {
                let renderer = ImageRenderer(content: summarySection)
                renderer.scale = scale
                image = renderer.uiImage
                shareSheetShown = true
            }
        }
        .sheet(isPresented: $shareSheetShown) {
            if let image = image {
                Image(uiImage: image)
            }
        }
    }
    
    private var summarySection: some View {
        Section {
            // ...
        }
    }
}


Еще одна захватывающая особенность типа ImageRenderer - это возможность “привлечь” отрисованное изображение в любой экземпляр типа CGContext. Это означает, что вы можете использовать ImageRenderer, чтобы сделать изображение SwiftUI-вью частью вашего контекста PDF.

struct SummaryContainerView: View {
    var body: some View {
        List {
            summarySection
        }
        .toolbar {
            Button("Save as PDF") {
                let renderer = ImageRenderer(content: summarySection)
                renderer.render { size, renderInContext in
                    var box = CGRect(
                        origin: .zero,
                        size: .init(width: 600, height: 800)
                    )
                    
                    guard let context = CGContext(fileUrl as CFURL, mediaBox: &box, nil) else {
                        return
                    }
                    
                    context.beginPDFPage(nil)
                    renderInContext(context)
                    context.endPage()
                    context.closePDF()
                }
            }
        }
    }
    
    private var summarySection: some View {
        Section {
            // ...
        }
    }
}

 

Как видно из примера выше, мы создаем экземпляр CGContext  чтобы получить PDF-файл. Мы используем функцию render из типа ImageRenderer, позволяющую нам получить доступ к размерам изображения при отображении SwiftUI-вью, а так же функцию визуализатора, с помощью которой мы можем “ввести” изображение в экземпляр типа CGContext.

Надеюсь, вам понравился этот пост. Не стесняйтесь задавать мне вопросы по нему в Twitter. Спасибо за чтение, до следующей недели!
 

Оригинал статьи

Содержание