見出し画像

SwiftUIでいこう! - ドラッグ & ドロップ

公式ドキュメントです。

参考サイトです。

少し似ているものとしてドラッグジェスチャー

すごく簡単にできます。参考サイトのコードをコピペ。これだけでテキストを" TextField"にドラッグ&ドロップできます。XcodeのPlaygroundで実行できます。

import SwiftUI
import PlaygroundSupport
struct Sample11View: View {
 @State private var getText: String = ""
 let str = "「SwiftUIとドラッグ&ドロップ」サンプル"
 var body: some View {
    VStack {
       Text(str)
          .onDrag {
             NSItemProvider(object: self.str as NSString)
          }
          .padding()
       TextField("ここにドロップして", text: $getText)
          .padding()
          .border(Color.black)
          .padding()
    }
    
 }
}
// playgroundで実行する場合に必要なコード
PlaygroundPage.current.setLiveView(Sample11View())

参考サイトをみながら画像をドラッグ&ドロップするアプリを作ってみます。

Xcodeで新規プロジェクトを作ってViewを作っていきます。画像をグリッドに表示させるところからです。

最初から作られているContentViewの他に、struct Home:Viewとして別にViewを作り、このHome()ContentViewに入れ込むことで表示させる仕組みです。

ScrollViewでLazyGridを使って表示させます。そのデータ取得するコードですが、まずデータを指定する元となる構造体を作ります。idとimageとして画像の名前を入れるようにします。


struct Img:Identifiable {
   var id:Int
   var image:String
}

この構造体をつ使って実際の表示データを取得します。事前に画像データをXcodeに入れておきます。今回は2枚の画像を入れています。

class ImgData:ObservableObject{
   @Published var totalImage:[Img] = [
   Img(id: 0, image: "p1"),Img(id: 1, image: "p2")
   ]
   
   @Published var selectImg:[Img] = []
}

@ObservableObjectの指定をしてクラスを作ります。データ更新の監視が行われます。

@ObservedObject

これは@Stateとよく似たものになります。
@StateでViewの状態管理は可能ですが、複数の値を状態管理するとなるとプロパティの宣言が多くなり、管理が大変になります。
そこで、@ObservedObjectを利用すると、そういったプロパティをひとまとめにしたオブジェクトとして管理することができます。
付与するオブジェクトはObservableObjectプロトコルに準拠する必要があります。
また、監視するプロパティは@Publishedを付与します。

Home()です。 ScrollView、LazyVGridを使って画像を全面に配置します。

変数colsで配列を作りその配列のデータをループして予め入れておいた画像を表示していきます。

struct Home:View {
   var cols = Array(repeating: GridItem(.flexible(), spacing:15), count:2)
   
   @ObservedObject var delegate = ImgData()
   var body:some View{
       VStack(spacing:15){
           
           ScrollView{
               LazyVGrid(columns: cols,spacing: 20){
                   ForEach(delegate.totalImage){image in
                       Image(image.image)
                           .resizable()
                           .frame(height:150)
                           .cornerRadius(15)
                   }
               }
           }
       }
   }
}

画像1

こんな感じになっています。次にドロップ、画像を移動する場所を作っていきます。画像のないときは"Drop Here!"と表示するようにします。基本的には先に作っているScrollViewと同じです。delegate.selectImgをForEachでループで取得するようにしています。そして表示する画像は

.frame(width:100,height:100)

と大きさを確保します。

  ZStack {
              if delegate.selectImg.isEmpty{
                   Text("Drop Here!")
                       .fontWeight(.bold)
                       .foregroundColor(.blue)
               }
               
               
               ScrollView(.horizontal,showsIndicators:false){
                   HStack{
                       ForEach(delegate.selectImg){image in
                           if image.image != ""{
                               Image(image.image)
                                   .resizable()
                                   .frame(width:100,height:100)
                                   .cornerRadius(15)
                           }
                       }
                      Spacer(minLength: 0)
                   }
               }
               .contentShape(Rectangle())
               .background(Color.white)
           }

ドロップ&ドラッグできるようにしていきまうす。まず、importします。

import MobileCoreServices

そしてonDragです。この命令を書いてやるとドラッグができるようになります。var body:some Viewのすぐ下、画像表示部分 に追記していきます。

Image(image.image)
.resizable()
.frame(height:150)
.cornerRadius(15)

.onDrag{
NSItemProvider(item: .some(URL(string: image.image)! as NSSecureCoding), typeIdentifier: String(kUTTypeURL))
}

そして受けて、onDropを作っていきます。

ZStack {}

で作ったドラッグエリアの下に

.padding(.bottom,UIApplication.shared.windows.first?.safeAreaInsets.bottom)
.padding(.top,10)
.frame(height:delegate.selectImg.isEmpty ? 100 :nil)
.contentShape(Rectangle())
.background(Color.white)
.onDrop(of: [String(kUTTypeURL)], delegate:delegate)

書きます。場所の大きさを指定する

.frame(height:delegate.selectImg.isEmpty ? 100 :nil)

が重要です。そして クラスImgDataにDropDelegateを準拠させて、

class ImgData:ObservableObject,DropDelegate{}

ドロップできるようにする関数

  func performDrop(info: DropInfo) -> Bool {
       for provider in info.itemProviders(for: [String(kUTTypeURL)]){
           if provider.canLoadObject(ofClass: URL.self){
               print("url loaded")
               let _ = provider.loadObject(ofClass: URL.self) { (url,err) in
                   print(url!)
                   
                   let status = self.selectImg.contains{ (check) ->Bool in
                       if check.image == "\(url!)"{return true}
                       else{return false}
                       
                   }
                   
                   if !status{
                       DispatchQueue.main.async {
                           withAnimation(.easeOut){
                               self.selectImg.append(Img(id: self.selectImg.count, image: "\(url!)"))
                           }
                       }
                   }
               }
           }
           else{
               print("cannot loader")
           }
       }
       
       return true
   }

を設置します。

func performDrop(info: DropInfo) -> Bool {}

に実装することでドロップできるようになります。

let _ = provider.loadObject(ofClass: URL.self) { (url,err) in
                   print(url!)
          let status = self.selectImg.contains{ (check) ->Bool in
                       if check.image == "\(url!)"{return true}
                       else{return false}
            }
          if !status{
                       DispatchQueue.main.async {
                           withAnimation(.easeOut){
                               self.selectImg.append(Img(id: self.selectImg.count, image: "\(url!)"))
                           }
                       }
             }
 }

この部分で変数selectImgに画像のデータが重複しないように追加できるようにしています。(ドロップしていない画像のみ追加できる)

変数selectImgに追加することで

ScrollView(.horizontal,showsIndicators:false){} ・・・

が実行されドロップエリアに表示されるということです。

"NSItemProvider""func performDrop(info: DropInfo) -> Bool {}"がポイントのようです。

この記事が気に入ったらサポートをしてみませんか?