[TIL / 25.02.11] 사이드메뉴를 구현해보자 . . . !

2025. 2. 11. 19:03·iOS/Swift

1. 미리보는 결과

 

옆에서 펼쳐지는 메뉴를 지금껏 햄버거바라고 했는데,, 찾아보니깐 사이드 메뉴라고 하더라구요?!

이를 라이브러리를 써서 구현하는 방법도 있으나 이 방법은 이미지난 포스트에서 다뤘던 것처럼 상단 탭바에서도 썼기에,, 

직접 구현하는 방법은 많이 어려운가해서 찾아보고 있었는데

 

https://world-of-larooly.tistory.com/88

 

Swift Side Menu UI 만들기 (with Code)

이번에 만들 UI는 명칭이 좀 다양한데요. 사이드바, 메뉴바, 햄버거 메뉴, 슬라이드바 등등 부르는 이름이 다양한데 일단 편의상 사이드 메뉴(Side Menu) 라고 부르겠습니다. 또한 만드는 방법이 다

world-of-larooly.tistory.com

 

요 선생님 코드 참고해서 만들어보았습니다.

 

솔직히 보고 얼추 이해는 되는데 아직 모르는 부분이 꽤 많고, 저는 모든 UI를 스토리보드 없이 코드로만 구현하는 방식을 선호해서 이 부분은 어떻게 대치를 해야하나 하다가 결국 오늘도 Claude의 도움을 받고 말았습니다...

 

일단 제 프로젝트는 ViewController로 모든걸 해결하진 않고, View, ViewController, ViewModel 간 역할을 나눠 구성되어있어서 저 블로그 내 코드도 분리시켰습니다. (클로드를 통해..)

 

2. 구현

class SideMenuView: UIView {
    
    // 사이드메뉴를 펼치고 접을때 필요한 그림자뷰, 메뉴뷰
    private var shadowView: UIView!
    private var menuView: UIView!
    
    // isExpanded로 메뉴의 펼쳐짐 상태 관리, 그 상태에 따라 바뀔 Constraint
    private var trailingConstraint: NSLayoutConstraint!
    private var isExpanded: Bool = false
    
    // 터치를 감지를 해야하기에 GestureRecognizer 선언
    private var tapGesture: UITapGestureRecognizer?
    
    // 디자인 상 메뉴바의 너비
    private let menuWidth: CGFloat = 345

    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupUI()
        
        // 이 뷰를 다른 뷰와 같이 쓸건데 이건 후에 설명
        isUserInteractionEnabled = false
    }
    
    // 스토리보드 없이 쓸거라 이렇게 처리
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented.")
    }
    
    private func setupUI() {
        setupShadowView()
        setupMenuView() // ShadowView 위에 MenuView를 올리는 구조
        setupConstraints()
    }
    
    // 그림자 뷰 설정
    private func setupShadowView() {
        shadowView = UIView(frame: bounds) // SideMenuView의 bound에 맞춰 UIView 생성
        shadowView.autoresizingMask = [.flexibleWidth, .flexibleHeight] // 부모 뷰의 너비와 높이에 맞춰 리사이징
        shadowView.backgroundColor = .black
        shadowView.alpha = 0.0 // 메뉴를 펼치기 전엔 보이면 안되기에 투명하게
        
        addSubview(shadowView)
    }
    
    private func setupMenuView() {
        menuView = UIView()
        menuView.backgroundColor = UIColor(named: "Bg 1")
        addSubview(menuView)
    }
    
    private func setupConstraints() {
        menuView.snp.makeConstraints {
            $0.width.equalTo(menuWidth)
            $0.top.bottom.equalToSuperview()
        }
        
        trailingConstraint = menuView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: menuWidth)
        trailingConstraint.isActive = true
    }
    
    @objc private func handleTap() {
        if isExpanded {
            toggleMenu()
        }
    }
    
    func toggleMenu() {
        
        print("toggleMenu")
        
        isExpanded.toggle()
        
        isUserInteractionEnabled = isExpanded
        
        if isExpanded {
            tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
            shadowView.addGestureRecognizer(tapGesture!) // 메뉴가 펼쳐지면 탭을 감지할 수 있어야 함.
        } else {
            if let gesture = tapGesture {
                shadowView.removeGestureRecognizer(gesture) // 메뉴가 접히게 되면 기존 존재하던 tapGesture를 없애줘야 함.
            }
        }
        
        let targetPosition = isExpanded ? 0 : menuWidth // trailing를 걸어둘 수치를 확장 상태에 따라 정함
        
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0, options: .layoutSubviews) {
            self.trailingConstraint.constant = targetPosition // constraint 수치 변경해 이동
            self.layoutIfNeeded() // constraint를 변경하는 것이기에 다시 레이아웃하도록
            self.shadowView.alpha = self.isExpanded ? 0.6 : 0.0 // 쉐도우 뷰 또한 확장 여부에 따라 투명도 조절
        }
    }
    
}

 

우선 가장 포인트가 될 사이드메뉴 뷰. 원래 뷰컨트롤러도 만들어서 사이드메뉴를 쓰는 뷰컨트롤러가 상속받는 구조를 생각했는데, 그 뷰컨트롤러는 그 뷰를 담고 토글하면 어떻게 처리해줄지에 대한 것만 담을 뿐이라 굳이 필요없겠다고 판단했습니다. 

 

그럼 결국 구조가 SideMenu를 필요로 하는 뷰컨트롤러인 HomeViewController, 그 안에 뷰를 책임지는 HomeView, 동시에 사이드메뉴 UI 관련 처리를 담당하는 SideMenuView가 되겠습니다.

HomeViewController - HomeView, SideMenuView

 

이제 홈 뷰컨트롤러쪽에서 이를 어떻게 쓰는지 다뤄보자면

view.addSubview(homeView)
homeView.snp.makeConstraints {
    $0.edges.equalToSuperview()
}
        
view.addSubview(sideMenuView)
sideMenuView.snp.makeConstraints {
    $0.horizontalEdges.bottom.equalToSuperview()
    $0.top.equalTo(view.safeAreaLayoutGuide.snp.top)
}

 

우선 최상위 뷰에 두 뷰를 서브뷰로 설정해 각자의 터치 이벤트 등에 대한 처리를 명확하게 하면서, sideMenuView를 homeView의 위에 두었습니다. 아까 SideMenuView에서 ShadowView의 UserInteractionEnabled를 false로 설정한 이유도 이것에 있습니다.

앱 실행 후 홈에 들어오면 홈 화면에 있는 버튼들과 상호작용해야하기에 쉐도우뷰는 잠시 비활성화하도록 한겁니다.

 

@objc func handleMenuToggle() {
        sideMenuView.toggleMenu()
    }
    
extension HomeViewController: HomeViewDelegate {
    
    func homeViewDidTapSideMenu() {
        print("sideMenu Tap")
        handleMenuToggle()
    }
    
}

 

protocol HomeViewDelegate: AnyObject {
    func homeViewDidTapSideMenu()
    func homeViewDidTapPost()
    func didSearch(_ query: String)
    func didSelectTag(_ tag: String)
}

// HomeView.swift 중
private func setupActions() {
        SideMenuButton.addTarget(self, action: #selector(didTapSideMenuButton), for: .touchUpInside)
    }
    
@objc private func didTapSideMenuButton() {
        delegate?.homeViewDidTapSideMenu()
    }

 

홈뷰에서 넣어두었던 햄버거바 버튼에 대한 메서드를 뷰컨트롤러 측에서 담당할 수 있게 homeViewDelegate를 이용했었는데, 이에 대한 내용을 구현한 코드입니다.

결국 사이드메뉴 뷰 안에서 정의된 애니메이션 관련 코드를 호출하도록 하여 isExpanded의 값에 따라 메뉴를 펼치고 접게 됩니다.

 


 

블로그에 있는 코드 모르는 부분은 전부 찾아보고 클로드에 적어서 파악 후에, 내 코드 구조에 맞추는 과정이 꽤 까다로웠다.

각 코드의 역할을 분류하고 정리했어야 했고, 결국 오늘도 AI의 주도하에 진행되긴 했다.

스스로 UI 관련 응용력이 부족하다고 느끼는데, 실력을 키워 이런 커스텀 컴포넌트 정도는 알아서 80%이상 구현할 수 있게 되어야 한다고 생각한다. 

전엔 저런 코드를 AI에 물어봐 설명시켜도 이해를 잘 못했는데 이해도 자체는 전보다 훨씬 좋아져서 실력이 늘고 있긴하네..!라고 생각하게 된다. 화이탱

'iOS > Swift' 카테고리의 다른 글

[TIL / 25.02.14] 열거형 Enum을 이용한 메뉴 구성  (2) 2025.02.14
[TIL / 25.02.13] 다중 섹션 및 헤더가 포함된 사이드 메뉴 구현  (0) 2025.02.13
[TIL / 25.02.10] 헤더를 이용한 테이블뷰를 구성, 셀과 헤더 높이 자동 계산  (4) 2025.02.10
[TIL / 25.02.07] 라이브러리 사용해서 상단 탭바 구현하기 2  (0) 2025.02.07
[TIL / 25.02.06] 라이브러리 사용해서 상단 탭바 구현하기 1  (5) 2025.02.06
'iOS/Swift' 카테고리의 다른 글
  • [TIL / 25.02.14] 열거형 Enum을 이용한 메뉴 구성
  • [TIL / 25.02.13] 다중 섹션 및 헤더가 포함된 사이드 메뉴 구현
  • [TIL / 25.02.10] 헤더를 이용한 테이블뷰를 구성, 셀과 헤더 높이 자동 계산
  • [TIL / 25.02.07] 라이브러리 사용해서 상단 탭바 구현하기 2
subkyu-ios
subkyu-ios
subkyu-ios 님의 블로그 입니다.
  • subkyu-ios
    subkyu-ios 님의 블로그
    subkyu-ios
  • 전체
    오늘
    어제
    • 분류 전체보기 (51)
      • iOS (35)
        • Swift (35)
      • 내일배움캠프 (7)
      • Git, Github (3)
      • Algorithm (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Wil
    stackview
    내일배움캠프
    사전캠프
    회고
    UIKit
    tabman
    KPT
    ios
    프로그래머스
    본캠프
    TableView
    til
    github
    의존성 주입
    RxSwift
    트러블슈팅
    algorithm
    Swift
    알고리즘
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
subkyu-ios
[TIL / 25.02.11] 사이드메뉴를 구현해보자 . . . !
상단으로

티스토리툴바