ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift Bottom Sheet 바텀시트 구현하는 방법 + 동적 높이 할당
    개발/Swift 2023. 5. 24. 17:02

    Modern Collection View 와 MVVM 패턴 가이드

     

    [iOS] Modern Collection View & MVVM 패턴 가이드 - 인프런 | 강의

    MVVM 패턴과 Modern Collection View를 사용해 네트워킹을 구현하고, 다양하고 동적인 Collection View를 자유자재로 다룰 수 있게 됩니다., - 강의 소개 | 인프런

    www.inflearn.com

    보통 navigation 의 present 함수를 통해 버텀 시트를 구현 하면 좋지만

    해당하는 높이가 아닌 낮은 높이의 바텀시트를 구현 하기 위해서는 결국

    Custom 하게 만들어야 한다.

     

    그러려면 결국 ViewController 안에 배경을 흐릿하게 하고

    안에 Content View 를 넣어주어 해당 View를 BottomSheet 처럼 꾸며야한다.

     

    우선 띄울때 이전 화면에서 present로 띄우기 때문에

    BottomSheet VC init 에서 style 를 넣어준다.

    이렇게 하면 pageSheet 형태로 뜨지 않고 전체 영역을 먹음

    self.modalPresentationStyle = .overCurrentContext
    

    컨테이너 뷰를 정의해준다. 이게 바텀시트가 될예정

    흰 배경에 Radius 넣어주고 Clips 로 잘라주고

    위 왼쪽 오른쪽만 코너를 넣어준다.

    private let containerView = {
            let view = UIView(backgroundColor: .white500)
            view.clipsToBounds = true
            view.layer.cornerRadius = 10
            view.layer.maskedCorners = CACornerMask(arrayLiteral: .layerMinXMinYCorner, .layerMaxXMinYCorner)
            return view
        }()
    

    전체 view의 배경을 어둡게 해주고

    view.backgroundColor = .black.withAlphaComponent(0.8)
    

    바텀시트 안에 들어갈 UI 들을 저 containerView 안에 넣어주면 된다.

    여기서 만약 바텀시트처럼 밑에서 위로 올라오는 애니메이션을 구현하려면

    제약조건을 설정 해주면 된다.

    Container view 안에 들어갈 UI들의 높이 제약조건은 꼭 잘 설정해줘야한다.

    //viewdidLoad에서 높이 0으로 지정
    containerView.snp.makeConstraints { make in
                make.leading.trailing.bottom.equalToSuperview()
                make.height.equalTo(0)
            }
    
    //view did appear
    public override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            UIView.animate(withDuration: 0.3) { [weak self] in
                self?.containerView.snp.updateConstraints { make in
                    make.height.equalTo(536)
                }
                self?.view.layoutIfNeeded()
            }
        }
    

    애니메이션과 함께 높이를 변경 시켜주면 애니메이션 이 먹는다.

    닫을때 밑으로 내리고싶으면 컨테이너뷰의 bottom 을 밑으로 내려주면 된다

    UIView.animate(withDuration: 0.3) { [weak self] in
                self?.containerView.snp.updateConstraints { make in
                    make.bottom.equalTo(536)
                }
               self?.view.backgroundColor = .clear
               self?.view.layoutIfNeeded()
           } completion: { [weak self] _ in
               self?.dismiss(animated: true, completion: completion)
           }
    

     

    그런데 만약 높이가 동적이라면?

     

    이 바텀시트 안에 Tableview를 사용해서 높이를 저렇게 지정해 줄수 없다면 조금 달라진다.

    처음제약 조건에 containerview는 어차피 테이블뷰 높이에 따라 바뀌므로

    tableview를 container 에 맞춰준후 높이 0을 넣어준다.

    //viewdidLoad
    
    containerView.snp.makeConstraints { make in
                make.leading.equalToSuperview()
                make.trailing.equalToSuperview()
                make.bottom.equalTo(0)
            }
            
            tableView.snp.makeConstraints { make in
                make.leading.equalTo(20)
                make.trailing.equalTo(-20)
                make.top.bottom.equalToSuperview()
                make.height.equalTo(0)
            }
    

    높이를 지정해주지 않으면 cell provider 자체가 호출이 안되서 cell 이 그려지지도 않느다.

    여기서 높이를 구하느라 삽질을 많이했느데

    가장 효과적인 방법은 Observing을 사용하는거이었다.

    tableView.rx.observe(CGSize.self, #keyPath(UITableView.contentSize))
            .debounce(.milliseconds(100), scheduler: MainScheduler.instance)
            .bind { [weak self] size in
    						///이렇게 높이를 트래킹 할수가 있다.
    
                guard let s = self, var height = size?.height else { return }
    						//아래 로직은 디바이스보다 커지는 경우를 막기 위함
                if height > Config.Device.SCREEN_HEIGHT - 60 {
                    height = Config.Device.SCREEN_HEIGHT - 60
                }
                s.tableViewHeight = height
                // 높이 지정
                UIView.animate(withDuration: 0.3) {
                    s.pointCouponTableView.snp.updateConstraints { make in
                        make.height.equalTo(height)
                    }
                    self?.view.layoutIfNeeded()
                }
            }.disposed(by: disposeBag)

    닫는 부분은 위와 같이 containerview bottom을 아래로 내려주면 된다.

    위에서 s.tableViewHeight 를 저장해 두었기 때문에 그걸 쓰면 된다.

    댓글

Designed by Tistory.