1
2

【SwiftUI】iOS 用のスクラッチ カード UI の実装

Last updated at Posted at 2023-12-25

はじめに

はじめまして、Hridoy Chandra Das(リド)(@ihridoydas)です。

SwiftUI で、Google Pay 特典のスクラッチ カードのようなスクラッチ カードを作成します。

スクラッチカードは競技用に設計されたカードで、多くの場合、PIN を隠すために薄いカードストックまたはプラスチックで作られており、1 つまたは複数の領域に隠された情報が含まれており、不透明なカバーを剥がすことで明らかになります。

ユーザーはデバイスの画面上でカードを仮想的に「スクラッチ」「指で擦る」し、その下に隠されたコンテンツを明らかにすることができます。 このコンテンツは、クーポン コード、割引、その他の特典を紹介するためによく使用されます。

s1.jpg

SwiftUI の.mask Modifier(Instance Property)を使用すると、あるビューを別のビューでマスクできます。 簡単な例から始めましょう。

Mask

maskは、レンダリング システムが指定されたビューにアルファを適用するビュー。

こちらで.mask Modifierを使用して二つ方法でスクラッチビュー作成のついて共有したいと思います。(Path、Canvas)

方法:1(Path)

PathView.swift
    // MARK: Scratchable overlay view
       RoundedRectangle(cornerRadius: 20)
          .frame(width: 250, height: 250)
             Image("bg", bundle: nil)
             .resizable()
             .frame(width: 250, height: 250)
             .cornerRadius(20)
               

    // MARK: Hidden content view
            Image("bg", bundle: nil)
             .resizable()
             .frame(width: 250, height: 250)
             .cornerRadius(20)
             .overlay {
                Image("won", bundle: nil)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200)
                }
                 .mask(
                    Path { path in
                     path.addLines(currentPathState)
                 }.stroke(style: StrokeStyle(lineWidth: 50, lineCap: .round, lineJoin: .round))
                        )
                    .gesture(
                        DragGesture(minimumDistance: 0, coordinateSpace: .local)
                    .onChanged({ value in
                        currentPathState.append(value.location)
                    })
                )

完全な作成ビュー(Path) : ScratchCardView

ScratchCardView.swift
import SwiftUI

struct ScratchCardView: View {
    @State var currentPathState = [CGPoint]()
    //navigationBarTitle
    init() {
           UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "Georgia-Bold", size: 20)!]
       }
    
    var body: some View {
        NavigationView {
            VStack {
                ZStack{
                    // MARK: Scratchable overlay view
                    RoundedRectangle(cornerRadius: 20)
                        .frame(width: 250, height: 250)
                    Image("bg", bundle: nil)
                        .resizable()
                        .frame(width: 250, height: 250)
                        .cornerRadius(20)
               

                    // MARK: Hidden content view
                    Image("bg", bundle: nil)
                        .resizable()
                        .frame(width: 250, height: 250)
                        .cornerRadius(20)
                        .overlay {
                            Image("won", bundle: nil)
                                .resizable()
                                .scaledToFit()
                                .frame(width: 200)
                        }
                        .mask(
                            Path { path in
                                path.addLines(currentPathState)
                            }.stroke(style: StrokeStyle(lineWidth: 50, lineCap: .round, lineJoin: .round))
                        )
                        .gesture(
                            DragGesture(minimumDistance: 0, coordinateSpace: .local)
                                .onChanged({ value in
                                    currentPathState.append(value.location)
                                })
                        )
                }
                
            }
            .navigationBarTitle (Text("Scratch Card"), displayMode: .automatic)
            .toolbar{
                ToolbarItemGroup(placement: .navigationBarTrailing){
                    Button{
                        currentPathState.removeAll()
                    } label: {
                        Label("Clear",systemImage: "clear.fill")
                    }
                }
            }
            .accentColor(.red)
        }
        
    }
}

struct ScratchCardViewPreview: PreviewProvider {
    static var previews: some View {
        ScratchCardView()
    }
}

方法:2(Canvas)

Canvas.swift
   // MARK: Scratchable overlay view
       RoundedRectangle(cornerRadius: 20)
          .frame(width: 250, height: 250)
             Image("bg", bundle: nil)
                  .resizable()
                  .frame(width: 250, height: 250)
                  .cornerRadius(20)

    // MARK: Hidden content view
             Image("bg", bundle: nil)
                  .resizable()
                  .frame(width: 250, height: 250)
                  .cornerRadius(20)
                  .overlay {
             Image("won", bundle: nil)
                  .resizable()
                  .scaledToFit()
                  .frame(width: 200)
                }
                // Mask the Canvas
                .mask(
                    Canvas { context, _ in
                        for line in lines {
                            var path = Path()
                            path.addLines(line.points)
                            context.stroke(path,
                                 with: .color(.white),
                                style: StrokeStyle(lineWidth:line.lineWidth,
                                        lineCap: .round,
                                        lineJoin: .round)
                     )}
                   }
                )
                // User Can gesture and change the point value
                 .gesture(
                     DragGesture(minimumDistance: 0, coordinateSpace: .local)
                 .onChanged({ value in
                          let newPoint = value.location
                            currentLine.points.append(newPoint)
                            lines.append(currentLine)
                }))

完全な作成ビュー(Canvas) : ScratchCardCanvasView

ScratchCardCanvasView.swift
import SwiftUI

struct Line {
    var points = [CGPoint]()
    var lineWidth: Double = 50.0
}

struct ScratchCardCanvasView: View {
    @State private var currentLine = Line()
    @State private var lines = [Line]()
    
    init() {
           UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "Georgia-Bold", size: 20)!]
       }
    
    var body: some View {
        
        NavigationView {
            VStack {
                ZStack {
                    // MARK: Scratchable overlay view
                    RoundedRectangle(cornerRadius: 20)
                        .frame(width: 250, height: 250)
                    Image("bg", bundle: nil)
                        .resizable()
                        .frame(width: 250, height: 250)
                        .cornerRadius(20)

                    // MARK: Hidden content view
                    Image("bg", bundle: nil)
                        .resizable()
                        .frame(width: 250, height: 250)
                        .cornerRadius(20)
                        .overlay {
                            Image("won", bundle: nil)
                                .resizable()
                                .scaledToFit()
                                .frame(width: 200)
                        }
                        .mask(
                            Canvas { context, _ in
                                for line in lines {
                                    var path = Path()
                                    path.addLines(line.points)
                                    context.stroke(path,
                                                   with: .color(.white),
                                                   style: StrokeStyle(lineWidth: line.lineWidth,
                                                                      lineCap: .round,
                                                                      lineJoin: .round)
                                    )
                                }
                            }
                        )
                        .gesture(
                            DragGesture(minimumDistance: 0, coordinateSpace: .local)
                                .onChanged({ value in
                                    let newPoint = value.location
                                    currentLine.points.append(newPoint)
                                    lines.append(currentLine)
                                })
                        )
                }
            }
            .navigationBarTitle (Text("Scratch Card Canvas"), displayMode: .automatic)
            .toolbar{
                ToolbarItemGroup(placement: .navigationBarTrailing){
                    Button{
                        currentLine.points.removeAll()
                        lines.removeAll()
                    } label: {
                        Label("Clear",systemImage: "clear.fill")
                    }
                }
            }
            .accentColor(.red)
        }
  
    }
}

struct ScratchCardCanvasViewPreview: PreviewProvider {
    static var previews: some View {
        ScratchCardCanvasView()
    }
}

結果:

GitHub サンプル

以上、最後までお読みいただきありがとうございました。

Scratch_view_ios.gif
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2