카테고리 없음

Today I Learn: UIScrollView + UIStackView를 UITableView로 리팩토링

Goniii 2025. 3. 14. 11:35

미니 프로젝트 후, 튜터님께서 피드백 해주신 내용을 바탕으로 리팩토링을 진행했다 

 

상세 피드백

참고: 코멘트 중요도

P0: 다음 프로젝트에서는 반드시 고쳐졌으면 하는 사항.

P1: 현업에서 중요시하는 요소이거나 기타 중요한 이유로 고쳐지기를 권장하는 사항.

P2: 개인 취향 혹은 질문 사항.

 

개인 피드백 

  • P1: 비슷한 데이터의 경우, 하나하나 만들기 보다는 UITableView등을 사용해 표시하면 좋습니다.
  • P2: View의 Layout을 작성할 때 동일한 방법을 사용하면 좋지 않았을까 합니다.

 

리팩토링 전 와이퍼 프레임 분석

프로필 이미지, 이름, 나이, mbti, 깃허브, 블로그 주소, 자기소개까지는 유저 생성 시 필수로 입력 받는 값이다

따라서 View 내부에서 직접 뷰와 라벨을 통해 필수 값들을 표시했다

팀 응원, 목표는 유저를 등록할 때 자유롭게 커스텀 할 수 있도록 정보의 수, 내용 길이에 제한을 두지 않으므로 동적으로 height을 잡을 수 있도록 해야 했다

커스텀 할 수 있는 정보의 개수에 제한이 없으므로 뷰는 위와 같이 크게 UIScorllView > 필수 뷰, UIStackView(커스텀 정보)로 구성되어 있다

 

유저 생성 화면

 

커스텀한 정보 데이터에 따라 View를 만들고 UIStackView에 추가하는 형식으로 구현했었다.

        if let contents = user.contents {
            contents.forEach({ content in
                guard let title = content.title, let content = content.content else {
                    return
                }
                let infoView = SooInfoView(title: title, content: content)
                contentStackView.addArrangedSubview(infoView)
            })
        }

user.contents : 팀원 등록 시 추가한 정보

 

 

 

우선 리팩토링을 하는 이유. 즉, 피드백을 주신 이유에 대해 고민해봤다

  • 같은 UI를 사용하는 데이터를 StackView로 구현하기 보단 UITableView를 써서 Cell로 구현하는 게 속도 적으로 효율적이다
  • UIStackView를 이용하여 레이아웃을 잡는 시간을 줄일 수 있을 거 같다
  • 구현 당시, UITableView를 생각하지 않았던 건 아니지만, 스크롤 뷰에 테이블뷰를 추가했을 때, 테이블 뷰의 높이가 정해지지 않아 스크롤이 안 되는 문제가 발생했었다
  • 프로젝트 기간이 짧아 스크롤이 안 되는 정확한 이유에 대해 찾지 못하고 UIStackView로 대체했었다

 

 

수정사항 (첫 번째 시도): UIStackView → UITableView

동적인 데이터를 UITableView의 Cell로 설정

  • 기존의 UIStackView 내부에 View를 넣어 구현하던 형태를 UITableView로 교체
  • 상단 프로필과 자기소개는 필수 데이터이므로 아래 예시의 ‘팀 응원’부터는 UITableView로 교체

    1. UIScrollView 내부 UITableView가 존재하는 것을 권장하지 않음
    2. 스크린 충돌 위험
      • UIScrollView, UITableView 모두 수직 스크롤이 가능하기 때문에 제대로 스크롤이 되지 않는 문제가 발생함
    3. UITableView의 contentSize 문제
      • 체감상 가장 문제였던 부분이다
      • UIScrollView 내부에 스크롤이 가능하려면 contentSize의 높이를 확실하게 정해줘야 한다. 그러나 UITableView의 Cell에 들어오는 데이터는 동적이기 때문에 UITableView의 Height을 확실하게 정해주지 못한다
      • estimatedRowHeight, rowHeight을 설정해도 먹통이었다
          tableView.estimatedRowHeight = 60 // 예상 높이 (적절한 값으로 설정)
          tableView.rowHeight = UITableView.automaticDimension // 자동 계산
      • UITableView의 height을 임의로 설정해주지 않으면 테이블뷰는 공간을 차지하지 않았다.
       

 

 

수정사항 (두 번째 시도): UIScrollView → UITableView

https://explorer89.tistory.com/328

 

✅ UIScrollView 안에 UITableView를 넣는 것이 올바른가? (상세페이지 구현)

일반적으로 UIScrollView 안에 UITableView를 넣는 것은 권장되지 않습니다.이유는 두 개의 스크롤 기능이 충돌할 수 있기 때문입니다.❌ 문제점스크롤 충돌UIScrollView와 UITableView 모두 수직 스크롤이 가

explorer89.tistory.com

UIScrollView 내부에 같은 스크롤 기능을 하는 UITableView를 넣는 것을 권장하지 않는다는 포스팅을 보게 되었다

 

 

현재까지는 프로필 정보와 동적 뷰를 모두 UIScrollView 내부에 넣어 구현하고 있었다

UITableView는 내부적으로 ScrollView를 구현하므로 ScrollView 내부에 TableView를 넣는 건 비효율적일 수 있다

→ 따라서 UIScrollView를 제거하고 UITableView 하나에 Section을 나누어 구현

extension SooTemplateViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        return 2 // 프로필, 커스텀 컨텐츠
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section {
        case 0: return 1        // 프로필 셀
        case 1: return 1 + (user?.contents?.count ?? 0) // 커스텀 컨텐츠 셀 (자기소개 포함)
        default:
            return 1
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch indexPath.section {
        case 0: // 프로필 셀
            guard let cell = tableView.dequeueReusableCell(withIdentifier: SooProfileCell.id) as? SooProfileCell else {
                return UITableViewCell()
            }
            guard let user else { return cell }
            
            // 깃허브 탭 제스처 구현
            let gitButtonTapGesture = CustomTapGesture(target: self, action: #selector(openURLTapGeusture))
            gitButtonTapGesture.openURL = user.gitHubPathURL
            cell.gitButton.addGestureRecognizer(gitButtonTapGesture)
            
            // 블로그 탭 제스처 구현
            let blogButtonTapGesture = CustomTapGesture(target: self, action: #selector(openURLTapGeusture))
            blogButtonTapGesture.openURL = user.blogPathURL
            cell.blogButton.addGestureRecognizer(blogButtonTapGesture)
            
            cell.config(user: user)
            return cell
        case 1: // 커스텀 컨텐츠 셀 (자기소개 포함)
            guard let cell = tableView.dequeueReusableCell(withIdentifier: SooCustomInfoCell.id) as? SooCustomInfoCell else {
                return UITableViewCell()
            }
            let contents = user?.contents ?? []
            let userContent = ([Content(title: "자기소개", content: user?.introduce)] + contents)[indexPath.row]
            guard let title = userContent.title, let content = userContent.content else { return cell }
            cell.config(title: title, content: content)
            return cell
        default:
            return UITableViewCell()
        }
    }
}

extension SooTemplateViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        switch indexPath.section {
        case 0: return 400
        default: 
            return UITableView.automaticDimension
        }
    }
    
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        return UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 1))
    }
    
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 30
    }
}

 

 

https://github.com/nbc-team01/team-card-app/issues/53

 

refactoring/Soo · Issue #53 · nbc-team01/team-card-app

피드백 내용 리팩토링 UserListViewController P2: NavigationBar를 숨기고 별도의 Custom View로 해당 역할을 대체한 이유가 궁금합니다. CreateUserCardViewController P1: 유저를 생성해도 UserListViewController에 바로 반

github.com

 

728x90