iOS 연락처 앱 개발 - 심화 구현 및 문제 해결

버질·2024년 12월 10일

오늘은 기존에 구현했던 연락처 앱에서 더 나아가 이미지 마스크 처리, 테이블 뷰 셀 재사용 문제 해결, UI 디테일 개선 등 심화 과제를 진행하며, iOS 앱 개발에서 중요한 개념과 기술을 배웠다. 이 과정에서 해결했던 문제들과 적용한 기술을 정리해본다.

1. 이미지 마스크 처리

문제
랜덤으로 불러온 포켓몬 이미지의 크기가 제각각이라, 이미지가 원형 프로필 뷰를 초과하거나 잘리는 문제가 발생했다. 이로 인해 UI가 불완전해 보였다.

해결 방법
CAShapeLayer와 UIBezierPath를 사용해 원형 마스크를 설정:
이미지가 원형 안에 정확히 들어가도록 마스크를 적용했다.
테두리(border)를 추가하여 깔끔한 디자인을 유지.
적용 코드 (ContactTableViewCell)

private func setupUI() {
    profileImageView.layer.cornerRadius = 30
    profileImageView.clipsToBounds = true
    profileImageView.contentMode = .scaleAspectFill
    profileImageView.layer.borderWidth = 1
    profileImageView.layer.borderColor = UIColor.gray.cgColor

    // 원형 마스크 적용
    let circularPath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 60, height: 60))
    let mask = CAShapeLayer()
    mask.path = circularPath.cgPath
    profileImageView.layer.mask = mask
}

배운 점
UIBezierPath와 CAShapeLayer:
복잡한 UI 요소를 디자인할 때 강력한 도구.
이미지가 마스크를 넘지 않도록 클리핑(clipping) 처리하는 방식이 유용했다.
프로필 이미지와 테두리:
UI 디테일은 작은 차이로 큰 차이를 만들어낸다. 단순히 원형으로 만드는 것이 아니라, 테두리와 마스크를 추가하면서 완성도를 높였다.

2. 테이블 뷰 셀 재사용 문제

문제
테이블 뷰를 스크롤할 때, 이전 셀의 데이터가 재사용되는 현상이 발생했다. 이로 인해:
이전에 설정된 이미지가 새로운 셀에 남아있는 경우.
텍스트가 업데이트되지 않는 문제.

해결 방법
prepareForReuse() 메서드 구현:
셀이 재사용되기 전에 초기화하여 이전 데이터가 남아있지 않도록 처리.
적용 코드 (ContactTableViewCell)

override func prepareForReuse() {
    super.prepareForReuse()

    // 셀 초기화
    profileImageView.image = nil
    nameLabel.text = nil
    phoneLabel.text = nil
}

배운 점
UITableViewCell의 재사용 메커니즘:
테이블 뷰는 성능을 위해 셀을 재사용하지만, 이전 상태가 남아있으면 UI 문제가 발생할 수 있다.
prepareForReuse() 메서드를 활용하면 이 문제를 효과적으로 해결할 수 있다.

3. UI 디테일 및 버튼 동작 개선

문제
테이블 뷰가 "추가" 버튼의 터치 이벤트를 방해하는 문제가 발생.
버튼이 클릭되지 않는 현상이 발생하거나, 잘못된 위치에 배치된 경우도 있었다.

해결 방법
버튼의 위치와 레이아웃 확인:
"추가" 버튼의 크기를 명확히 설정하고, 다른 UI 요소와 겹치지 않도록 조정.
버튼 색상과 스타일 변경:
버튼의 가시성을 높이기 위해 tintColor를 설정.

적용 코드 (MainViewController)

override func viewDidLoad() {
    super.viewDidLoad()
    setupUI()
}

private func setupUI() {
    title = "친구 목록"

    // "추가" 버튼 설정
    let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addContact))
    addButton.tintColor = .systemBlue
    navigationItem.rightBarButtonItem = addButton

    tableView.dataSource = self
    tableView.delegate = self
    tableView.register(ContactTableViewCell.self, forCellReuseIdentifier: "ContactCell")
    tableView.rowHeight = 80
    view.addSubview(tableView)
    tableView.frame = view.bounds
}

배운 점
UI 디버깅의 중요성:
버튼이 UI 요소와 겹쳐 작동하지 않을 때, 레이아웃을 철저히 디버깅해야 한다는 점.
NavigationItem의 활용:
UINavigationItem을 통해 깔끔한 상단 UI를 만들 수 있었다.

4. 데이터 유효성 검증 및 에러 처리

문제
사용자 입력값(이름, 전화번호)이 비어 있는 상태로 저장되거나, 중복된 연락처가 저장되는 문제가 발생.
네트워크 연결이 실패할 경우 이미지를 불러오지 못해 앱이 비정상적으로 작동.

해결 방법
유효성 검증 추가:
이름과 전화번호를 입력하지 않으면 경고 메시지를 표시.
중복 연락처 방지:
이미 저장된 데이터와 비교하여 중복을 방지.
네트워크 에러 처리:
네트워크 연결 실패 시 기본 이미지를 설정하고, 사용자에게 경고 메시지를 표시.

적용 코드

@objc private func saveContact() {
    guard let name = nameField.text, !name.isEmpty else {
        showAlert(title: "오류", message: "이름을 입력하세요.")
        return
    }
    guard let phone = phoneField.text, !phone.isEmpty else {
        showAlert(title: "오류", message: "전화번호를 입력하세요.")
        return
    }

    if isDuplicateContact(name: name, phone: phone) {
        showAlert(title: "오류", message: "중복된 연락처입니다.")
        return
    }

    // 데이터 저장 로직...
}

func isDuplicateContact(name: String, phone: String) -> Bool {
    let contacts = DataManager.shared.fetchAllContacts()
    return contacts.contains { $0.name == name && $0.phoneNumber == phone }
}

배운 점
입력값 검증은 필수:
데이터를 저장하기 전에 유효성을 검증하지 않으면 사용자가 비정상적인 데이터를 입력할 수 있다.
에러 처리의 중요성:
네트워크 실패나 잘못된 입력을 적절히 처리해야 앱이 안정적으로 동작한다.

오늘 느낀 점

기능 구현과 디테일의 차이:
기능만 구현하는 것에서 그치지 않고, UI 디테일과 사용자 경험을 개선하는 데 신경을 써야 한다는 점을 배웠다.
예를 들어, 프로필 이미지를 원형으로 처리하는 것만으로도 앱의 디자인 완성도가 크게 향상된다.

코드 유지보수성을 위한 구조화:
prepareForReuse와 같은 메서드를 적절히 활용하면, 코드가 더 간결해지고 유지보수가 쉬워진다.

유효성 검증과 에러 처리:
유효성 검증은 단순한 데이터 검증 이상으로, 앱의 안정성을 높이는 데 중요한 역할을 한다는 것을 깨달았다.

profile
iOS Developer · SwiftUI & UIKit '가끔 되고 가끔 안 되는' 문제를 뿌리부터 잡습니다.

0개의 댓글