관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - SwiftUI] Swipe Down To Dismiss (아래로 스와이프하여 뷰 dismiss하는 방법) 본문

iOS 응용 (SwiftUI)

[iOS - SwiftUI] Swipe Down To Dismiss (아래로 스와이프하여 뷰 dismiss하는 방법)

jake-kim 2022. 9. 30. 22:50

Swipe Down To Dismiss 구현 아이디어

  • .gesture()와 DragGesture를 사용하여 drag 제스쳐 구현
    (gesture와 DragGesture 개념을 모른다면, 이전 포스팅 글 참고)
  • gesture 안에서 y축이 얼마나 변했는지와, y축 velocity 값을 가지고 dismiss할것인지 결정
  • dismiss할때는 NavigationLink의 인수 중 Binding 프로퍼티인 isActive값을 false로 변경

구현

  • 편리를 위해 CGSize extension으로 - operator 구현
extension CGPoint {
  static func - (lhs: Self, rhs: Self) -> Self {
    CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
  }
}
  • 필요한 프로퍼티 선언
    • draggedOffset: 드래그하는 만큼 뷰도 움직이게 하기위해 Binding하여 사용될 프로퍼티
    • isActive: NavigationLink의 인수로 들어가며, dismiss할때 사용
import SwiftUI
import Combine

struct ContentView: View {
  @State private var draggedOffset = CGSize.zero
  @State private var isActive = false
  
  var body: some View {
  }
}
  • body부분 뷰 구현
  var body: some View {
    NavigationView {
      NavigationLink(
        isActive: $isActive,
        destination: {
          Image("background")
            .offset(draggedOffset)
            .gesture(swipeDownToDismiss)
        },
        label: {
          Text("다음 화면")
        }
      )
    }
  }
    
  var swipeDownToDismiss: some Gesture {
    // TODO
  }
  • swipeDownToDimsss 구현
  var swipeDownToDismiss: some Gesture {
    DragGesture()
      .onChanged { gesture in
        guard 
        gesture.location > 30, // NavigationView의 왼쪽 끝에서 오른쪽으로 하면 dimisss되는 기능을 사용할 때 제스쳐 처리 방지 guard문
        gesture.translation.height > 0 // 아래로만 드래그 되도록 구현
        else { return }
        draggedOffset = gesture.translation
      }
      .onEnded { gesture in
        if checkIsDismissable(gesture: gesture) {
          isActive = false
        }
        draggedOffset = .zero
      }
  }
  
  func checkIsDismissable(gesture: _ChangedGesture<DragGesture>.Value) -> Bool {
    // TODO
  }
  • checkIsDismissable 구현
    • (여기서 한계값은 임의로 경험적으로 정한 것)
    • veolocity를 구할때는 gesture.predictedEndLocation에 현재 위치를 빼면 gesture의 속도를 쉽게 알아내기가 가능
  func checkIsDismissable(gesture: _ChangedGesture<DragGesture>.Value) -> Bool {
    let dismissableLocation = gesture.translation.height > 50
    let dismissableVolocity = (gesture.predictedEndLocation - gesture.location).y > 50
    return dismissableLocation || dismissableVolocity
  }

List를 사용할땐, dismiss 방법

  • dismiss 방법
    • List를 사용 X - isActive 프로퍼티를 사용
    • List를 사용 O - @Environment(\.presentationMode) private var presentationMode 사용
      * List를 사용하지 않을때 @Environment(\.presentationMode)를 사용해도 동작하지 않으므로 주의
  • List에서 @Environment(\.presentationMode)를 사용하지 않고 isActive를 사용하면 엉뚱한 Cell이 보여질 수 있으므로 @Environemnt를 사용 권장
presentationMode.wrappedValue.dismiss()

 

 

* 전체 코드: https://github.com/JK0369/ExSwipeDownToDismiss-SwiftUI

Comments