Memo

メモ > 技術 > IDE: Xcode > SwiftUIの作例

SwiftUIの作例
■お絵かきツール SwiftUICatalog/DrawingApp at master - SatoTakeshiX/SwiftUICatalog - GitHub https://github.com/SatoTakeshiX/SwiftUICatalog/tree/master/DrawingApp ContentView.swift
import SwiftUI struct ContentView: View { @State private var endedDrawPoints: [DrawPoints] = [] @State private var selectedColor: DrawColor = .black var body: some View { VStack { // キャンバス Canvas( endedDrawPoints: $endedDrawPoints, selectedColor: $selectedColor ) // 操作用UI HStack(spacing: 10) { Spacer() Button(action: { selectedColor = .black }) { Text("描く") } Button(action: { selectedColor = .clear }) { Text("消す") } Spacer() } } } } #Preview { ContentView() }
DrawPoints.swift
import SwiftUI struct DrawPoints: Identifiable { var id = UUID() var points: [CGPoint] var color: Color } enum DrawColor { case black case clear var color: Color { switch self { case .black: return Color.black case .clear: return Color.white } } }
Canvas.swift
import SwiftUI struct Canvas: View { @State private var tmpDrawPoints: DrawPoints = DrawPoints(points: [], color: .black) @Binding var endedDrawPoints: [DrawPoints] @Binding var selectedColor: DrawColor var body: some View { ZStack { // 外枠を描画 Rectangle() .foregroundColor(Color.white) .border(Color.black, width: 2) // endedDrawPointsに保存された線を描画 ForEach(endedDrawPoints) { data in Path { path in path.addLines(data.points) } .stroke(data.color, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round)) } // 最後にtmpDrawPointsに保存された線を描画 Path { path in path.addLines(tmpDrawPoints.points) } .stroke(tmpDrawPoints.color, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round)) } .gesture( // ドラッグで線を描画 DragGesture(minimumDistance: 0) .onChanged({ (value) in tmpDrawPoints.color = selectedColor.color tmpDrawPoints.points.append(value.location) }) .onEnded({ (value) in endedDrawPoints.append(tmpDrawPoints) tmpDrawPoints = DrawPoints(points: [], color: selectedColor.color) }) ) } }
■お絵かきツール(画像を保存する機能を追加) Info.plist にKey「Privacy - Photo Library Additions Usage Description」を追加し、Valueに「画像を保存します。」と記載しておく ファイルの内容を直接確認すると、dictタグ内に以下が追加されている Info.plist
<key>NSPhotoLibraryAddUsageDescription</key> <string>画像を保存します。</string>
UIView+Extension.swift
import UIKit extension UIView { var renderedImage: UIImage { // 自身の矩形情報を取得する let rect = self.bounds // ビットマップの内容を作成する UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) let context: CGContext = UIGraphicsGetCurrentContext()! self.layer.render(in: context) // ビットマップから画像を取得する let capturedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return capturedImage } }
ContentView に capture メソッドを追加する ContentView.swift
func capture(rect: CGRect) -> UIImage { // 矩形情報からUIWindowを作成 let window = UIWindow(frame: CGRect(origin: rect.origin, size: rect.size)) // 自身をUIViewControllerに変換 let hosting = UIHostingController(rootView: self.body) // windowのview階層にUIViewControllerのViewを組み込んで表示させる hosting.view.frame = window.frame window.addSubview(hosting.view) window.makeKeyAndVisible() // viewをキャプチャ return hosting.view.renderedImage }
ContentView に saved を追加し、 さらに「var body: some View」の直下に GeometryReader を追加し、 さらに保存ボタンとその処理も追加する SwiftUIの肝となるGeometryReaderについて理解を深める - Qiita https://qiita.com/masa7351/items/0567969f93cc88d714ac ContentView.swift
@State var saved: Bool = false var body: some View { GeometryReader { geometry in VStack { // キャンバス Canvas( endedDrawPoints: $endedDrawPoints, selectedColor: $selectedColor ) // 操作用UI HStack(spacing: 10) { Spacer() 〜略〜 Button(action: { // 画像を取得 let captureImage = capture(rect: geometry.frame(in: .global)) // 画像を保存 UIImageWriteToSavedPhotosAlbum(captureImage, nil, nil, nil) // 保存完了アラートを表示 saved = true }) { Text("保存") }.alert(isPresented: $saved, content: { Alert( title: Text("保存"), message: Text("画像が保存されました。") ) }) Spacer() } } } }
これで「写真」アプリに画像を保存できる ただし現状では、お絵かきのためのUIも含めて保存されているので要改良 ■お絵かきツール(UIを除外して画像を保存する機能を追加) ContentView に canvasRect を追加し、引数として Canvas に渡す ContentView.swift
@State private var canvasRect: CGRect = .zero @State var saved: Bool = false var body: some View { GeometryReader { geometry in VStack { // キャンバス Canvas( endedDrawPoints: $endedDrawPoints, selectedColor: $selectedColor, canvasRect: $canvasRect )
Canvas 側で値を受け取り、GeometryReader を使って、表示されたタイミングで値を更新する (ContentView にも更新が検知される) Canvas.swift
@Binding var canvasRect: CGRect var body: some View { GeometryReader { geometry in ZStack { Rectangle() .foregroundColor(Color.white) .border(Color.black, width: 2) .onAppear { canvasRect = geometry.frame(in: .local) }
ContentView.swift
private func cropImage(with image: UIImage, rect: CGRect) -> UIImage? { // 意図した箇所を切り抜きたいので、画像のスケール情報に合わせて切り取りたい矩形情報を拡大 let ajustRect = CGRect(x: rect.origin.x * image.scale, y: rect.origin.y * image.scale, width: rect.width * image.scale, height: rect.height * image.scale) // 画像を切り抜く guard let img = image.cgImage?.cropping(to: ajustRect) else { return nil } //UIImageに変換 return UIImage(cgImage: img, scale: image.scale, orientation: image.imageOrientation) }
ContentView.swift
Button(action: { // 画像を取得 let captureImage = capture(rect: geometry.frame(in: .global)) // 画像を切り抜き let croppedImage = cropImage(with: captureImage, rect: canvasRect) // 画像を保存 UIImageWriteToSavedPhotosAlbum(croppedImage!, nil, nil, nil) // 保存完了アラートを表示 saved = true }) {

Advertisement