본문 바로가기
iOS/develop log

🚀 iOS 모달 UI 최적화 - 공통 컴포넌트로 유지보수성 향상하기

by 개발하는 감자입니다 2025. 2. 20.
728x90

📌 문제 상황

 

모달(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의 크기를 동적으로 설정

UIScrollViewcontentLayoutGuide를 활용하여 스크롤이 정상적으로 동작하도록 설정

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 조합을 통해 옵션 개수에 따라 동적으로 크기 조절 가능

UIScrollViewcontentLayoutGuide를 활용하여 자동 스크롤 처리 가능

옵션 개수가 많거나 적어도 정상적으로 동작하도록 개선됨


🎯 이번 개선으로 해결된 점

모달 헤더(ModalHeaderView) 분리 → 중복 코드 제거

StackView + ScrollView 레이아웃 문제 해결 → 동적 UI 대응 가능

유지보수성 증가 → 기능 확장 시 수정해야 할 코드 최소화

 

이번 개선 작업을 통해, iOS 개발에서 공통 요소를 분리하는 것이 유지보수성을 높이고 코드 품질을 개선하는 데 얼마나 중요한지 다시 한번 느낄 수 있었다.

앞으로도 코드의 재사용성을 높이고, 불필요한 중복을 제거하는 방식으로 지속적인 개선을 이어갈 예정이다. 🚀

📌 마무리하며

이번 최적화를 통해 유지보수성, 재사용성, 확장성을 고려한 구조를 만들었다.

이제 새로운 모달을 추가할 때, 중복되는 헤더 UI를 직접 구현할 필요 없이 ModalHeaderView를 활용하면 된다.

728x90
반응형