【SwiftUI】 動的に増えるオブジェクトにジェスチャーを付ける
SwiftUI を触り始めたばかりではありますが、動的に増える view に対してジェスチャーを追加する方法が分からず検索しても view を定義してからそこにジェスチャーを追加する方法くらいしかあまり見つからずチュートリアルとかも無さそうだったので、今回まとめてみたいと思います。
Swift UI で MVVM モデル
Swift UI はデータ(状態)を元に画面を表示してくれます。以前のViewController にはview もロジックも書くことが可能でしたが、Swift UI でレンダリングする Struct のbody には基本的に view を返すものしか記述できません。そのため、ある view にジェスチャーを追加してドラッグ出来る様にするには以下の様に実装します。
import SwiftUI
struct TestView: View {
@State var position: CGSize = CGSize(width: 200, height: 300)
var drag: some Gesture {
DragGesture()
.onChanged{ value in
self.position = CGSize(
width: value.startLocation.x
+ value.translation.width,
height: value.startLocation.y
+ value.translation.height
)
}
.onEnded{ value in
self.position = CGSize(
width: value.startLocation.x
+ value.translation.width,
height: value.startLocation.y
+ value.translation.height
)
}
}
var body: some View {
Image("hoge")
.position(x: position.width, y: position.height)
.gesture(drag)
}
}
ですがこれだと予め宣言していた Image 一つにしかジェスチャーを付けられません。このイメージをボタンを押すたびに増やしてそれぞれにジェスチャーを追加したい場合はどうすれば良いか少し悩みました。
しかしこれは MVVM の形で UIImage や position を持つモデルを VM で増やしたり位置を変えたりする事で実装可能でした。
Model
import UIKit
struct Pictures {
var pictures = [Picture]()
struct Picture: Identifiable, Hashable {
let id: Int
var x: CGFloat
var y: CGFloat
var picture: UIImage
fileprivate init(picture: UIImage, x: CGFloat, y: CGFloat, id: Int) {
self.picture = picture
self.x = x
self.y = y
self.id = id
}
}
private var uniquePictureId = 0
mutating func addPicture(_ picture: UIImage, x: CGFloat, y: CGFloat) {
uniquePictureId += 1
pictures.append(Picture(picture: picture, x: x, y: y, id: uniquePictureId))
}
}
View Model
import UIKit
class PictureViewModel: ObservableObject {
@Published private var model: Pictures = Pictures()
var pictures: [Pictures.Picture] {model.pictures}
func addpicture(_ picture: UIImage, at location: CGSize) {
model.addPicture(picture, x: location.width, y: location.height)
}
func movepicture(_ picture: Pictures.Picture, by offset: CGSize) {
if let index = model.pictures.firstIndex(of: picture) {
model.pictures[index].x += offset.width
model.pictures[index].y += offset.height
}
}
}
View
import SwiftUI
struct ContentView: View {
@ObservedObject var picturesVM: PictureViewModel
var body: some View {
VStack {
Button(action: {
self.picturesVM.addpicture(UIImage(imageLiteralResourceName: "hoge"), at: CGSize(width: 100, height: 200))
self.picturesVM.addpicture(UIImage(imageLiteralResourceName: "hoge2"), at: CGSize(width: 100, height: 200))
}){
Image(systemName:"photo.on.rectangle")
}.padding(.trailing)
ForEach(self.picturesVM.pictures) { picture in
Image(uiImage: picture.picture)
.resizable()
.scaledToFit()
.gesture(dragPicture(picture: picture))
.position(x: picture.x, y: picture.y)
}
}
}
func dragPicture(picture: Pictures.Picture) -> some Gesture {
DragGesture()
.onChanged{ value in
self.picturesVM.movepicture(picture, by: CGSize(
width: value.translation.width,
height: value.translation.height
))
}
.onEnded{ value in
self.picturesVM.movepicture(picture, by: CGSize(
width: value.translation.width,
height: value.translation.height
))
}
}
}
ObservableObject プロトコルを実装しているクラスのインスタンスを view 側で @ObservedObject を付けて持つ事でデータの変化を view の方で監視してくれます。
つまりviewModel で Model のリストを増やしたり位置を変えたりするとそれが view 側でレンダリングされるという事です。
以上で動的に増えるオブジェクトにジェスチャーを追加してドラッグする事ができました。
Swift UI 入門に
詳細!SwiftUI iPhoneアプリ開発入門ノート iOS 13+Xcode 11対応【電子書籍】[ 大重美幸 ]
SwiftUI 徹底入門 [ 金田 浩明 ]
[増補改訂第3版]Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 [ 石川 洋資、西山 勇世 ]
ディスカッション
コメント一覧
まだ、コメントがありません