Computer >> 컴퓨터 >  >> 스마트폰 >> iPhone

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

이 글에서는 Swift를 사용해 스포티파이(Spotify) 홈 화면 레이아웃을 코드로 직접 재현해 보겠습니다. 왜 프로그래밍 방식일까요? 다양한 방식으로 무언가를 만들 줄 아는 능력은 언제나 유용하고, 저 역시 코드로 직접 구현하는 것을 선호하기 때문입니다. 특히 팀 단위로 협업하거나 버전 관리 시스템(Git 등)을 사용할 때 이런 스킬이 큰 강점이 됩니다.

아래 이미지는 실제 스포티파이 모바일 앱의 홈 화면입니다. 이러한 레이아웃을 구현하기 위해 UICollectionView를 사용할 것이며, 하단 탭 내비게이션을 만들기 위해 TabBarController도 함께 활용할 수 있습니다.

기본 요구 사항: 시작하기 전에 Xcode 10 이상과 Swift 4 이상이 설치되어 있는지 확인하세요.
AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

1. 새 Xcode 프로젝트 생성 및 기본 설정

Xcode로 새 프로젝트를 생성하는 것부터 시작해 보겠습니다.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

ViewController.swift에서 가장 먼저 할 일은 슈퍼클래스를 UIViewController 대신 UICollectionViewController로 변경하는 것입니다. 우리 클래스는 collectionView를 기반으로 동작할 예정이기 때문입니다.

//
//  ViewController.swift
//  spotifyAutoLayout
//
//  Created by admin on 10/31/19.
//  Copyright © 2019 Said Hayani. All rights reserved.
//

import UIKit

class ViewController: UICollectionViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.backgroundColor = .purple
        // Do any additional setup after loading the view.
    }
}

이 상태에서 앱을 실행하면 빌드가 실패합니다. AppDelegate.swift 파일의 didFinishLaunchingWithOptions 함수 내부, return 문 앞에 다음 코드를 추가해야 합니다.

let layout = UICollectionViewFlowLayout()
window = UIWindow()
window?.rootViewController = ViewController(collectionViewLayout: layout)

완성된 코드는 다음과 같습니다.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    let layout = UICollectionViewFlowLayout()
    window = UIWindow()
    window?.rootViewController = ViewController(collectionViewLayout: layout)
    return true
}

이제 앱을 실행하면 backgroundColor가 보라색으로 변경된 것을 확인할 수 있습니다.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

2. CollectionView 필수 메서드 정의하기

다음 단계는 레이아웃을 구성하고 섹션 간 공간을 균등하게 분배하는 것입니다. CollectionView의 핵심 메서드들을 정의해 보겠습니다. 작업 순서는 다음과 같습니다.

  • 고유한 식별자(identifier)로 재사용 가능한 셀 등록
  • 섹션 내 아이템 개수 정의
  • 등록된 셀 사용

CollectionView의 일부 메서드를 원활하게 사용하려면 UICollectionViewDelegateFlowLayout을 채택해야 자동완성 기능까지 활용할 수 있습니다. 그럼 CollectionViewCell 등록부터 시작하겠습니다.

viewDidLoad() 안에서 collectionView.register() 메서드를 호출해 재사용 셀을 등록합니다.

collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)

그다음 numberOfItemsInSection을 사용해 collectionView에 표시될 셀의 개수를 정의합니다. 우선 5개로 설정해 보겠습니다.

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 5
}

다음은 cellForItemAt으로 재사용 셀을 정의하는 단계입니다. 이 메서드는 UICollectionViewCell을 반환해야 하며, 고유 식별자인 cellId를 사용합니다.

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
    cell.backgroundColor = .red
    return cell
}

지금까지의 전체 코드는 다음과 같습니다.

import UIKit

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
    let cellId : String = "cellId"

    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.backgroundColor = .purple
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
        cell.backgroundColor = .red
        return cell
    }
}

앱을 실행하면 빨간색 배경의 셀 5개가 화면에 표시되어야 합니다.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

3. 셀에 커스텀 너비와 높이 지정하기

이제 셀들을 올바르게 배치하고 widthheight를 지정해야 합니다. 각 셀은 화면 전체 너비를 차지하도록 만들어 보겠습니다.

다행히 sizeForItemAt 메서드를 사용하면 셀에 커스텀 크기를 지정할 수 있습니다. 이 메서드는 CGSize 타입을 반환해야 합니다.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = view.frame.width
    let height = CGFloat(200)

    return CGSize(width: width, height: height)
}

view.frame.width를 사용해 셀의 너비를 화면 너비와 동일하게 만들고, 높이는 CGFloat 타입으로 커스텀하게 지정했습니다.

시뮬레이터에서 결과를 확인해 보세요.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

4. 재사용 가능한 커스텀 셀 만들기

지금까지 잘 진행되고 있습니다. 이번에는 재사용 가능한 커스텀 셀을 만들어 보겠습니다. CustomCell이라는 이름의 새 Swift 파일을 생성하세요.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

CustomCell.swift의 기본 구조는 다음과 같습니다.

import UIKit

class CustomCell: UICollectionViewCell {
    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

이제 커스텀 셀을 지원하도록 두 메서드, 즉 collectionView.registercellForItemAt을 수정해야 합니다. 먼저 register 메서드에서 UICollectionViewCell.selfCustomCell로 교체합니다.

collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)

그다음 cellForItemAt에서 반환되는 셀을 CustomCell 타입으로 캐스팅합니다.

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell

앱을 실행해도 눈에 띄는 변화는 없을 것입니다. 그래서 CustomCell에 backgroundColor = .yellow를 적용해 보겠습니다. 이때 cellForItemAtcell.backgroundColor = .red 줄을 삭제하는 것도 잊지 마세요. 그러면 배경색이 노란색으로 바뀐 것을 확인할 수 있습니다.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

이제 본격적으로 CustomCell에 기능을 추가할 시간입니다 :D

실제 스포티파이 홈 화면을 보면 각 섹션(예제에서는 CustomCell)은 섹션 제목과 하위 셀들을 포함하며, 가로 방향으로 스크롤되는 구조입니다.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

5. 섹션 제목 추가하기

셀에 제목 레이블을 추가해 보겠습니다. CustomCell 클래스 안에 titleLabel 요소를 생성합니다.

let titleLabel: UILabel = {
    let lb = UILabel()
    lb.text = "Section Title"
    lb.font = UIFont.boldSystemFont(ofSize: 14)

    return lb
}()

그다음 init() 블록 안에서 해당 요소를 뷰에 추가합니다.

addSubview(titleLabel)

앱을 실행해도 여전히 변화가 없을 것입니다. 아직 요소에 제약 조건(constraint)을 걸지 않았기 때문입니다. 제약 조건을 적용하려면 titleLabel에 lb.translatesAutoresizingMaskIntoConstraints = false 속성을 반드시 추가해야 합니다.

titleLabel을 뷰에 추가한 후 제약 조건을 정의합니다.

addSubview(titleLabel)
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
titleLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true

항상 .isActive = true 속성을 붙이는 것을 잊지 마세요. 이 속성이 없으면 제약 조건이 작동하지 않습니다!

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

다음 단계로 넘어가기 전에 먼저 화면 배경색을 검정으로 변경하고, 셀의 노란색도 제거하겠습니다.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

6. 셀 안에 하위 셀(내부 CollectionView) 추가하기

이제 가장 중요한 부분입니다. 각 셀 안에 하위 셀들을 배치하는 것입니다. 이를 위해 CustomCell 내부에 CollectionView를 추가할 것입니다.

UICollectionViewCell 안에 CollectionView를 추가하려면 CustomCell이 UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource 프로토콜을 채택해야 합니다.

먼저 collectionView 요소를 일반적인 뷰처럼 생성해 보겠습니다.

let collectionView : UICollectionView = {
    // 레이아웃 초기화
    let layout = UICollectionViewFlowLayout()
    // 스크롤 방향을 가로로 설정
    layout.scrollDirection = .horizontal

    // collectionView 인스턴스 생성
    let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)

    // 제약 조건 활성화
    cv.translatesAutoresizingMaskIntoConstraints = false

    return cv
}()
참고: https://stackoverflow.com/questions/44614743/add-uicollectionview-in-uicollectionviewcell

처음 viewController.swift에서 했던 것처럼 초기화 시 layout을 collectionView에 주입했다는 점에 주목하세요. 여기서는 FlowLayout의 방향을 .horizontal(가로)로 지정했습니다.

이제 collectionView 요소를 서브뷰로 뷰에 추가하겠습니다. 코드를 깔끔하게 유지하기 위해 전용 함수를 하나 만들겠습니다.

fileprivate func setupSubCells(){
    // 뷰에 collectionView 추가
    addSubview(collectionView)

    collectionView.dataSource = self
    collectionView.delegate = self

    // 제약 조건 설정
    // CustomCell의 전체 공간을 꽉 채우도록 지정
    collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor).isActive = true
    collectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
    collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    collectionView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
}

collectionView의 delegate와 dataSource 모두 self로 설정했는지 확인하세요.

collectionView.dataSource = self
collectionView.delegate = self

그런 다음 init 블록 안에서 setupSubCells() 함수를 호출합니다.

아직 UICollectionViewDelegateUICollectionViewDelegateFlowLayout 프로토콜 요구사항을 충족하지 않았기 때문에 빌드 시 Xcode에서 오류가 표시될 것입니다. 해결하려면 먼저 하위 셀을 재사용 셀로 등록해야 합니다.

클래스 상단에 변수를 선언하고 cellId라고 이름을 짓습니다. 셀 식별자가 필요할 때마다 사용할 수 있습니다.

let cellId : String = "subCellID"
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)

오류를 모두 없애려면 두 메서드가 더 필요합니다. 섹션의 셀 개수를 정의하는 numberOfItemsInSection과 재사용 셀을 정의하는 cellForItemAt입니다. 이 메서드들은 collectionView가 정상적으로 작동하기 위한 필수 요소입니다.

// 셀 개수
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 4
}

// 재사용 셀
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
    cell.backgroundColor = .yellow

    return cell
}

결과는 다음과 같아야 합니다.

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

보시다시피 내부 collectionView의 배경은 보라색이고, 하위 셀들은 노란색으로 표시됩니다.

7. 하위 셀 크기 마무리 조정하기

마지막으로 subCells의 높이를 섹션 높이와 동일하게 맞추고, 너비도 높이와 같게 설정하겠습니다. 여기서도 sizeForItemAt을 사용해 셀의 heightwidth를 정의합니다.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    let width = frame.height
    let height = frame.height

    return CGSize(width: width, height: height)
}

그리고 완성입니다!

AutoLayout으로 iOS 스포티파이 클론 만들기: 코드만으로 구현하는 홈 화면 UI 튜토리얼

좋습니다! 이 글이 너무 길어지지 않도록 여기서 마무리하겠습니다. 두 번째 파트에서는 목업(Mockup) 이미지를 추가하고 실제 데이터로 화면을 채우는 방법을 다룰 예정입니다.

전체 소스 코드는 여기에서 확인하실 수 있습니다.

추가할 내용, 질문 또는 수정 사항이 있다면 아래 댓글로 남겨주시거나 Twitter로 연락해 주세요.

튜토리얼 두 번째 파트가 게시되면 알림을 받으실 수 있도록 이메일 리스트를 구독해 주세요!