Open7

【SwiftUI】Viewが持てる責務の粒度について

🐏🐏

久しぶりにSwiftを書くのでリハビリも兼ねて

🐏🐏

ユーザーが移動可能なView(Stampという名前とする)を実装する際に全ての機能を盛り込んでいいのかどうか

🐏🐏

例えば、ドラッグをする責務を持ったViewを定義してStampでそれを利用する方法が考えられる。

実装はAppleのDragGestureのドキュメントを参考に

struct Dragable<Content: View>: View {
    private let content: Content
    
    @GestureState private var dragState = DragState.inactive
    @State private var latestTranslation: CGSize = .zero
    
    init (_ contentBuilder: () -> Content) {
        content = contentBuilder()
    }
    
    init (_ content: Content) {
        self.content = content
    }
    
    var body: some View {
        let swipeGesture = LongPressGesture().sequenced(before: DragGesture())
            .updating($dragState) { gesture, current, transaction in
                switch (gesture) {
                case .first(true):
                    current = .pressing
                case .second(true, let dragging):
                    if (dragging == nil) {
                        let impactMed = UIImpactFeedbackGenerator(style: .medium)
                        impactMed.impactOccurred()
                    }
                    current = .dragging(translation: dragging?.translation ?? .zero)
                default:
                    current = .inactive
                }
            }.onEnded({ gesture in
                guard case .second(true, let dragging?) = gesture else { return }
                latestTranslation.width += dragging.translation.width
                latestTranslation.height += dragging.translation.height
            })
        return content
            .frame(alignment: .center)
            .offset(dragState.translation)
            .offset(latestTranslation)
            .simultaneousGesture(swipeGesture)
    }
}
🐏🐏

回転をする責務を持ったView

struct Rotatable<Content: View>: View {
    private let content: Content
    
    @GestureState private var rotateState = Angle(degrees: 0.0)
    @State private var angle = Angle(degrees: 0.0)
    
    init (_ contentBuilder: () -> Content) {
        content = contentBuilder()
    }
    
    init (_ content: Content) {
        self.content = content
    }
    
    var body: some View {
        let rotationGesture = RotationGesture()
            .updating($rotateState, body: { angle, current, transaction in
                current = angle
            })
            .onEnded({ angle in
                self.angle += angle
            })
        return content
            .rotationEffect(angle + rotateState, anchor: .center)
            .simultaneousGesture(rotationGesture)
    }
}

struct Rotatable_Previews: PreviewProvider {
    static var previews: some View {
        Rotatable(Text("Hello World").padding(20))
    }
}
🐏🐏

上記二つを利用するView

struct Stamp<Content: View>: View {
    private let content: Content
    
    init (_ contentBuilder: () -> Content) {
        content = contentBuilder()
    }
    
    init (_ content: Content) {
        self.content = content
    }
    
    var body: some View {
        Dragable {
            Rotatable {
                content
            }
        }
    }
}
🐏🐏

メリットとしては再利用可能性の向上と可読性の向上があげられ、
デメリットは記述量が増える?

🐏🐏

責務で処理が纏まる事も十分可読性の向上に繋がっているし、Stampの実装を読んだ時にコンテンツをドラッグ、回転させることができることが一目見てわかる