iOS/Toy project

[iOS : Toy Project] Head Space Focus (2) : Navigation

yevdev 2022. 6. 30. 20:38

➰ 이전 코드 내용

Head Space Focus

 

 

 

💡Head Space Focus(2)에서 할 것은 Navigation 구현!

- 상세 뷰로 넘어가게 하기!

 

❗️Navigation을 구현할 때는, 사용자가 최대한 개미지옥에서 탈출할 수 있도록 구현해줘야 함을 잊지말자!

 

 

 

 

1️⃣ 상세 뷰를 위한 다른 Storyboard, View Controller 만들기

- QuickFocusStoryboard

- QuickFocusListViewController

- 새로운 스토리보드의 Class와 Storyboard ID 까아쥐

 

 

2️⃣ 화면의 Component, AutoLayout 설정

- CollectionView 이용 → CollectionViewCell도 필요하겠지? "QuickFocusCell" 만들기

// QuickFocusCell.swift

import UIKit

class QuickFocusCell: UICollectionViewCell {
    
    @IBOutlet weak var thumbnailImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!

    func configure(_ quickFocus: QuickFocus) {
        thumbnailImageView.image = UIImage(named: quickFocus.imageName)
        titleLabel.text = quickFocus.title
        descriptionLabel.text = quickFocus.description
    }
}

 

 

3️⃣ Diffable Datasource 만들기

//  QuickFocusListViewController.swift

import UIKit

class QuickFocusListViewController: UIViewController {
    
    @IBOutlet weak var collectionView: UICollectionView!
    
    let breathingList = QuickFocus.breathing
    let walkingList = QuickFocus.walking
    
    // diffable datasource를 사용하기 위한, Section 정의
    // CaseIterable 프로토콜을 따르도록
    enum Section: CaseIterable {
        case breathe
        case walking
        
        // 각 Section별로 title을 정해주기
        var title: String {
            switch self {
            case .breathe: return "Breathing exercises"
            case .walking: return "Mindful walks"
            }
        }
    }
    
    typealias Item = QuickFocus
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }

}

→ CaseIterable 프로토콜을 따르게 한다면?

let allItems: [Section] = [.breathing, .walking]

대신, Section.allCases

로 불러올 수 있다.

 

→ 각 Section 별로 title을 위와 같이 설정해주면, 

let section: Section = .breathe

section.title

라고 코드를 작성하여, "Breathing exercises"라는 title을 가져올 수 있게 된다.

 

 

 

4️⃣ Presentation, Data, Layout

- Presentation : datasource

- Data : Snapshot

- Layout : compositional layout

//  QuickFocusListViewController.swift

import UIKit

class QuickFocusListViewController: UIViewController {
    
// ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // presentation
        datasource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "QuickFocusCell", for: indexPath) as? QuickFocusCell else {
                return nil
            }    // collectionView에서 재사용 Cell 가져오기
            cell.configure(item)    // 자동으로 업데이트 하게끔
            return cell
        })
        
        // data
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections(Section.allCases)   //  == snapshot.appendSections([.breathe, .walking])
        // breathingList를 breathe Section에
        // walkingList를 walking Section에 넣어주기
        snapshot.appendItems(breathingList, toSection: .breathe)
        snapshot.appendItems(walkingList, toSection: .walking)
        datasource.apply(snapshot)
        
        // layout
        collectionView.collectionViewLayout = layout()  // 너무 기니까 따로 빼서 만들기

    }
    
    private func layout() -> UICollectionViewCompositionalLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(50))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        // 내부 컨텐츠에 따라 높이가 달라질 경우, .estimated 사용
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
        // 수평방향으로 동일하게 3개의 group이 만들어 질 것 -> horizontal
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
        // item 간의 spacing
        group.interItemSpacing = .fixed(10)
        
        let section = NSCollectionLayoutSection(group: group)
        // section에 좌우패딩 주기
        section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 20, bottom: 30, trailing: 20)
        // group 간의 spacing
        section.interGroupSpacing = 20
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }


}

 

Compositional Layout을 어떻게 구성했나?!

layout 구성

 

 

5️⃣ Section Header Title을 넣어보자!

- Collection Reusable View 이용

Collection Reusable View

- UICollectionReusableView 파일 만들기 → "QuickFocusHeaderView"라고 명명

- 마찬가지로 Storyboard 설정에서 Custom Class와 Collection Reusable View의 identifier을 "QuickFocusHeaderView"로 설정

//  QuickFocusHeaderView.swift

import UIKit

class QuickFocusHeaderView: UICollectionReusableView {
    @IBOutlet weak var titleLabel: UILabel!
    
    func configure(_ title: String) {
        titleLabel.text = title
    }
}

 

- title datasource 만들기

- supplymentaryViewProvider을 통해 collectionView, kind, indexPath를 받아옴

//  QuickFocusListViewController.swift

import UIKit

class QuickFocusListViewController: UIViewController {   
    override func viewDidLoad() {
        super.viewDidLoad()   
        // presentation
        // ...
        
        datasource.supplementaryViewProvider = {(collectionView, kind, indexPath) in
            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "QuickFocusHeaderView ", for: indexPath) as? QuickFocusHeaderView else {
                return nil
            }
            let allSections = Section.allCases
            let section = allSections[indexPath.section]
            header.configure(section.title)
            return header
        }
        
        // ...
    }
    // ...
}

 

- header view 관련한 layout도 정의 마무리

//  QuickFocusListViewController.swift

let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [header]

 

 

6️⃣ Delegate 설정

- delegate를 self로 설정하고 프로토콜 준수까지 완료하기

//  FocusViewController.swift

import UIKit

class FocusViewController: UIViewController {
    // ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
       	// ...
        
        // 화면 넘어감을 위한 delegate 설정
        collectionView.delegate = self

    }
    // ...
}

// For delegate
extension FocusViewController: UICollectionViewDelegate {
    
    // collectionView에서 indexPath에 있는 item을 선택을 했을 때 실행되는 함수
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//        let item = items[indexPath.item]
        
        // 상세뷰로 넘어가는 storyboard 접근
        let storyboard = UIStoryboard(name: "QuickFocus", bundle: nil)
        // viewController도 가져와
        let vc = storyboard.instantiateViewController(withIdentifier: "QuickFocusListViewController") as! QuickFocusListViewController
        present(vc, animated: true) // 이전에 배웠던 모달 띄우기
    }
}

 

 

자, 여기까지 구현한 화면! 🚀

→ 일단 지금은 전에 구현해봤던 modal을 사용해서 상세페이지를 띄워봤지만, 이제 Navigation page로 나타나게 해보자!

 

 

 

7️⃣ Navigation Controller 설정

Navigation Embeded
large title 설정 및 "Focus" 타이틀 설정

// For delegate
extension FocusViewController: UICollectionViewDelegate {
    
    // ...
    
        let vc = storyboard.instantiateViewController(withIdentifier: "QuickFocusListViewController") as! QuickFocusListViewController
        navigationController?.pushViewController(vc, animated: true)
    }
}

present(vc, animated: true)

navigationController?.pushViewController(vc, animated: true)

 

navigation이 설정된 화면으로 넘어감 🚀

 

 

 

8️⃣ 상세 뷰의 navigation 바에 title 넣기!

vc.title = item.title   // 상세 화면으로 넘어갔을 때, title 나오게!

근데 이렇게 하면, large title로 적용되어서 나옴

그냥 작게 navigation 바에만 뜨게 하려면,

//  QuickFocusListViewController.swift
self.navigationItem.largeTitleDisplayMode = .never

로 설정

 

 

 

 


📌 정리,

  • Presentation → Diffable DataSource
  • Data → Snapshot
  • Layout → Compositional Layout
    - Compositional layout을 통해, Content 길이에 따라 group과 item의 길이가 자동으로 달라지게 하기! .estimated
    - group안의 itemd의 개수가 2개로 명확할 경우 
       → NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)

 

  • Section이 2개! → CaseIterable 프로토콜 사용해봄!
  • Navigation Controller 설정
  • Collection Reusable View를 이용한, Section의 Title 설정
    - UICollectionReusableView 파일 생성
    - title datasource 만들기 → supplymentaryViewProvider을 통해 collectionView, kind, indexPath를 받아옴
    - layout도 정의 마무리 → compositional layout에 맞춰서,,

 


Reference

  • 패스트 캠퍼스