メモ > 技術 > 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
}) {