관리 메뉴

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

[iOS - swift] 고정된 SheetView (TopSheetView, BottomSheetView) 구현 방법 본문

UI 컴포넌트 (swift)

[iOS - swift] 고정된 SheetView (TopSheetView, BottomSheetView) 구현 방법

jake-kim 2022. 3. 28. 23:24

topSheet

* 애플의 지도앱과 같이 pan 제스처에 따라 sheet가 동적으로 이동되는 뷰는 UISheetPresentationController 포스팅 글 참고

구현에 편리를 위해 사용한 프레임워크

  pod 'SnapKit'
  pod 'RxSwift'
  pod 'RxCocoa'
  pod 'RxGesture'

구현 아이디어

  • Sheet뷰의 핵심은 autolayout을 이용하여 위에서 나오도록 설정
  • 커스텀 뷰 안에 show(), hide()를 구현하여 여기서 autolayout을 변경하여 애니메이션이 동작하도록 구현
  • backgroundView와 contentView를 놓고 backgroundView는 dimmed처리용도, contentView는 컨텐츠를 담을 용도
  • autolayout 핵심 - topSheet처럼 contentView가 위에서 내려와야하므로, 초기 layout은 아래처럼 설정
self.contentView.snp.makeConstraints {
  $0.bottom.equalTo(self.snp.top)
  $0.width.equalToSuperview()
}
  • 아래로 내려오는 애니메이션에서는 아래처럼 레이아웃 설정 (애니메이션은 뒤에서 계속 설명)
self.contentView.snp.remakeConstraints {
  $0.top.left.right.equalToSuperview()
  $0.bottom.lessThanOrEqualTo(self.safeAreaLayoutGuide).inset(Metric.contentViewInset).priority(999)
}

구현 방법

  • 기본적인 뷰 구현
//  SheetView.swift

class SheetView: UIView {
  private enum Metric {
    static let cornerRadius = 14.0
    static let contentViewInset = UIEdgeInsets(top: 0, left: 0, bottom: 120, right: 0)
    static let sampleLabelTopSpacing = 120.0
    static let bottomLeftBottomRightCornerMask: CACornerMask = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
  }
  private enum Color {
    static let white = UIColor.white
    static let dimmedBlack = UIColor.black.withAlphaComponent(0.25)
    static let clear = UIColor.clear
  }
  
  private lazy var backgroundView: UIView = {
    let view = UIView()
    view.clipsToBounds = true
    return view
  }()
  private lazy var contentView: UIView = {
    let view = UIView()
    view.backgroundColor = Color.white
    view.layer.cornerRadius = Metric.cornerRadius
    view.layer.maskedCorners =  Metric.bottomLeftBottomRightCornerMask
    view.clipsToBounds = true
    return view
  }()
  private lazy var someButton: UIButton = {
    let button = UIButton()
    button.setTitle("예시 버튼", for: .normal)
    button.setTitleColor(.systemBlue, for: .normal)
    button.setTitleColor(.blue, for: .highlighted)
    return button
  }()
  private lazy var sampleLabel: UILabel = {
    let label = UILabel()
    label.text = "sample top sheet"
    label.textAlignment = .center
    return label
  }()
}
  • 핵심인 show와 hide 메소드 구현
    • hide할때는 뷰를 삭제하도록 하여, 사용하는 쪽에서 show()할때마다 새로운 인스턴스를 생성하도록 구현
  //  SheetView.swift
  func show(completion: (() -> Void)? = nil) {
    DispatchQueue.main.async {
      self.contentView.snp.remakeConstraints {
        $0.top.left.right.equalToSuperview()
        $0.bottom.lessThanOrEqualTo(self.safeAreaLayoutGuide).inset(Metric.contentViewInset).priority(999)
      }
      UIView.animate(
        withDuration: 0.3,
        delay: 0,
        options: .curveEaseInOut,
        animations: {
          self.backgroundView.backgroundColor = Color.dimmedBlack
          self.layoutIfNeeded()
        },
        completion: { _ in completion?() }
      )
    }
  }
  
  func hide(completion: (() -> Void)? = nil) {
    DispatchQueue.main.async {
      self.contentView.snp.remakeConstraints {
        $0.bottom.equalTo(self.snp.top)
        $0.left.right.equalToSuperview()
      }
      UIView.animate(
        withDuration: 0.3,
        delay: 0,
        options: .curveEaseInOut,
        animations: {
          self.backgroundView.backgroundColor = Color.clear
          self.layoutIfNeeded()
        },
        completion: { _ in
          completion?()
          self.removeFromSuperview()
        }
      )
    }
  }

 

사용하는 쪽

  • SheetView 인스턴스 생성
  • 레이아웃 설정
    • dimmed 효과가 보여야 하므로, 뷰에 꽉차게 레이아웃 설정
    • show()하는 순간, 위에서 아래로 내려오는 topSheet형태가 되어야 하므로, 애니메이션 전에 이미 뷰의 레이아웃이 자리잡도록 layoutIfNeeded()호출
// ViewController.swift

@objc private func didTapOpenButton() {
  let sheetView = SheetView()
  self.view.addSubview(sheetView)
  sheetView.snp.makeConstraints {
    $0.edges.equalToSuperview()
  }
  self.view.layoutIfNeeded()
  sheetView.show()
}

* 전체 코드: https://github.com/JK0369/ExSheetView

Comments