📌 문제 상황
모달(SelectionModal)을 개발하면서, 중복되는 코드가 많아 유지보수가 어려운 상황이 발생했다.
특히, 타이틀과 닫기 버튼이 있는 헤더 부분과 옵션 리스트를 담는 스크롤 뷰가 여러 모달에서 반복적으로 사용되는 문제가 있었다.
👉 비효율적인 코드 구조로 인해 발생한 문제점
• 모달마다 같은 헤더 코드가 중복됨
• 옵션 리스트의 동적 크기 조절이 어려움
• 코드가 길어질수록 유지보수가 어려워짐
• 재사용성이 부족해, 기능 확장 시 수정을 반복해야 함
이를 해결하기 위해 공통 요소를 분리하여 유지보수성을 높이고 중복을 최소화하는 구조로 개선했다.
🔍 Step 1: 공통 헤더(ModalHeaderView) 분리
기존에는 SelectionModal 내부에서 타이틀과 닫기 버튼을 직접 추가하여 UI를 구성했다.
하지만, 이 방식은 모든 모달에서 동일한 코드가 반복되는 비효율적인 구조였다.
✅ 해결 방법
• 헤더 부분을 별도의 뷰(ModalHeaderView)로 분리하여 다양한 모달에서 재사용 가능하도록 개선
• 콜백(onCloseTapped)을 추가하여, 모달을 사용하는 곳에서 닫기 동작을 설정할 수 있도록 유연한 구조로 변경
import UIKit
class ModalHeaderView: UIView {
private let titleLabel = UILabel()
private let closeButton = UIButton(type: .system)
var onCloseTapped: (() -> Void)? // 닫기 버튼 액션
init(title: String) {
super.init(frame: .zero)
setupUI(title: title)
addSubviews()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI(title: String) {
titleLabel.text = title
titleLabel.textAlignment = .center
titleLabel.font = UIFont.systemFont(ofSize: 18, weight: .bold)
titleLabel.textColor = .black
titleLabel.translatesAutoresizingMaskIntoConstraints = false
let configuration = UIImage.SymbolConfiguration(pointSize: 18, weight: .bold)
let closeImage = UIImage(systemName: "xmark", withConfiguration: configuration)
closeButton.setImage(closeImage, for: .normal)
closeButton.tintColor = .black
closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
closeButton.translatesAutoresizingMaskIntoConstraints = false
}
private func addSubviews() {
addSubview(closeButton)
addSubview(titleLabel)
}
private func setupConstraints() {
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: topAnchor, constant: 16),
closeButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
closeButton.widthAnchor.constraint(equalToConstant: 24),
closeButton.heightAnchor.constraint(equalToConstant: 24),
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
@objc private func closeTapped() {
onCloseTapped?()
}
}
✅ 변경 후 장점
• 모든 모달에서 동일한 헤더를 재사용 가능 → 중복 제거
• 모달별로 유동적인 타이틀 설정 가능 → 유연한 구조
• 콜백(onCloseTapped) 추가 → 뷰 컨트롤러에서 닫기 동작을 쉽게 설정 가능
🔍 Step 2: 모달 레이아웃 개선 (ScrollView + StackView)
모달 내부에는 옵션 리스트가 들어가는 UIStackView가 포함되어 있다.
하지만, UIStackView가 높이를 제대로 설정하지 못해 보이지 않는 문제가 발생했다.
👉 기존 문제점
1. UIScrollView 내부에서 UIStackView의 높이가 자동으로 설정되지 않음
2. UIScrollView가 콘텐츠 크기를 제대로 인식하지 못함
3. 옵션 개수가 많아질 경우, 스크롤이 정상적으로 동작하지 않음
✅ 해결 방법
• UIStackView의 크기를 동적으로 설정
• UIScrollView의 contentLayoutGuide를 활용하여 스크롤이 정상적으로 동작하도록 설정
private func setupConstraints() {
modalView.translatesAutoresizingMaskIntoConstraints = false
headerView.translatesAutoresizingMaskIntoConstraints = false
optionsScrollView.translatesAutoresizingMaskIntoConstraints = false
optionsStackView.translatesAutoresizingMaskIntoConstraints = false
modalHeightConstraint = modalView.heightAnchor.constraint(greaterThanOrEqualToConstant: 200)
modalHeightConstraint?.isActive = true
NSLayoutConstraint.activate([
modalView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
modalView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
modalView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
headerView.leadingAnchor.constraint(equalTo: modalView.leadingAnchor),
headerView.trailingAnchor.constraint(equalTo: modalView.trailingAnchor),
headerView.topAnchor.constraint(equalTo: modalView.topAnchor),
headerView.heightAnchor.constraint(equalToConstant: 52),
optionsScrollView.topAnchor.constraint(equalTo: headerView.bottomAnchor, constant: 15),
optionsScrollView.leadingAnchor.constraint(equalTo: modalView.leadingAnchor, constant: 20),
optionsScrollView.trailingAnchor.constraint(equalTo: modalView.trailingAnchor, constant: -20),
optionsScrollView.bottomAnchor.constraint(equalTo: modalView.bottomAnchor, constant: -15),
optionsStackView.widthAnchor.constraint(equalTo: optionsScrollView.frameLayoutGuide.widthAnchor),
optionsStackView.topAnchor.constraint(equalTo: optionsScrollView.contentLayoutGuide.topAnchor),
optionsStackView.leadingAnchor.constraint(equalTo: optionsScrollView.contentLayoutGuide.leadingAnchor),
optionsStackView.trailingAnchor.constraint(equalTo: optionsScrollView.contentLayoutGuide.trailingAnchor),
optionsStackView.bottomAnchor.constraint(equalTo: optionsScrollView.contentLayoutGuide.bottomAnchor),
optionsStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: 1) // ✅ 최소 높이 설정
])
}
✅ 변경 후 장점
• UIScrollView + UIStackView 조합을 통해 옵션 개수에 따라 동적으로 크기 조절 가능
• UIScrollView의 contentLayoutGuide를 활용하여 자동 스크롤 처리 가능
• 옵션 개수가 많거나 적어도 정상적으로 동작하도록 개선됨
🎯 이번 개선으로 해결된 점
✅ 모달 헤더(ModalHeaderView) 분리 → 중복 코드 제거
✅ StackView + ScrollView 레이아웃 문제 해결 → 동적 UI 대응 가능
✅ 유지보수성 증가 → 기능 확장 시 수정해야 할 코드 최소화
이번 개선 작업을 통해, iOS 개발에서 공통 요소를 분리하는 것이 유지보수성을 높이고 코드 품질을 개선하는 데 얼마나 중요한지 다시 한번 느낄 수 있었다.
앞으로도 코드의 재사용성을 높이고, 불필요한 중복을 제거하는 방식으로 지속적인 개선을 이어갈 예정이다. 🚀
📌 마무리하며
이번 최적화를 통해 유지보수성, 재사용성, 확장성을 고려한 구조를 만들었다.
이제 새로운 모달을 추가할 때, 중복되는 헤더 UI를 직접 구현할 필요 없이 ModalHeaderView를 활용하면 된다.
'iOS > develop log' 카테고리의 다른 글
🚀 머플러 앱 리팩토링 - 4. Domain Layer와 서비스 구조 설계 (0) | 2025.02.11 |
---|---|
🚀 머플러 리팩토링 - 3. MVVM 전환 과정에서의 고민과 새로운 접근 (0) | 2025.02.10 |
🚀 머플러 리팩토링 - 1. 리팩토링 계획 세우기 (레거시 코드 정리) (0) | 2025.01.29 |
🚀 머플러, 초심으로 돌아가는 설 연휴 프로젝트 (0) | 2025.01.27 |
iOS감자의 파란만장한 로그인 구현기 1. 로그인 구현의 시작 ( feat. 로그인 구현할 때 알아야 할 것들.) (0) | 2024.07.07 |