# UIKit5. 네비게이션 && 모달

이은호·2023년 2월 22일
0

swift

목록 보기
6/8
post-thumbnail

많은 기능은 곧 많은 화면전환을 의미한다.
화면전환은 크게 네비게이션과 모달로 나눠진다.

개요

모달

모달은 모달뷰와 알람등이 있으며 일시적으로 유저의 집중을 요구하는 컨텐츠를 표시할때 사용한다.
애플에서 제공해주는 모달

  • alert
  • activity views
  • share sheets
  • action sheets
    개발자가 직접 모달뷰를 제공할 수 있으며 여러 애니메이션스타일도 정할 수 있다. 앱을 이용한다면 이미 경험해본것이다.
    구현은 뷰컨의 present(_:animated:completion:) 메소드를 이용한다.

네비게이션

네비게이션같은경우 인스타의 파도타기(?)등등에서 유저가 회복할 수 있도록 개발을 해야한다. 아키텍쳐같은 경우엔 모든 앱이 각자의 특성에 맞게 개발되기에 특정한 스타일을 굳이 명명하기는 어렵다.

주요원칙:

  • 사용자가 언제나 명확한 이동경로를 파악할수 있게 해라
  • 정보 구조를 사용자가 이해하기 쉽고 빠르게 접근하도록 디자인 해야함
  • 사용자 제스쳐를 충분히 사용해라
    • 예를 들면, 스와이프백
  • iOS 에서 제공하는 네비게이션 주로 사용할것
    • 네비게이션 컨트롤러, 탭바컨트롤러 등등
  • 네비게이션바를 이용해서 현재 어떤 컨텍스트인지 잘 보여주기
  • 탭바를 이용해서 카테고리, 또는 기능별로 나누어서 표시해라
  • 아이패드에서는 탭바 보다, 스플릿 뷰를 써라
  • 여러 페이지가 있는 경우, 페이지 컨트롤을 써라

구현은 탭바컨트롤러와 네비게이션 컨트롤러를 이용한다.

결론

모달은 집중, 네비게이션은 말그대로 경로와 위치를 잘 알려줘야한다.

예제1

예전에 만들어본 앱에서 모달을 뛰우고 사파리를 띄우는 어플로 업그레이드 한다. 먼저 Detail이라는 스토리보드를 만들고 FrameworkViewController 파일을 만든다. 그리고 연결하고 ui를 만든다.

override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.delegate = self
        //자기 자신을 위임 -> 그래야 밑의 버튼 print가 기동함
    }
.
.
.
extension FrameworkListViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let framework = list[indexPath.item]
        print(">>> selected: \(framework.name)")
        
//        띄울 모달
        let storyboard = UIStoryboard(name: "Detail", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "FrameworkViewController") as! FrameworkViewController
        present(vc, animated: true)
    }
}

context전달

그러면 모달로 해당 데이터들을 전달해야만 한다. 어떻게 작성할까? 모달을 작성해주면서 전달하면 된다.

let sb = UIStoryboard(name: "Detail", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "FrameworkViewController") as! FrameworkViewController
vc.framework = framework
present(vc, animated: true)
import UIKit

class FrameworkViewController: UIViewController {
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var frameworkName: UILabel!
    @IBOutlet weak var frameworkDescription: UILabel!
    var framework: AppleFramework = AppleFramework(name: "Unknown", imageName: "", urlString: "", description: "")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        updateUI()
    }
    
    func updateUI() {
        imageView.image = UIImage(named: framework.imageName)
        frameworkName.text = framework.name
        frameworkDescription.text = framework.description
    }

}

웹페이지로 이동

이미 썼던것을 이용해 간단하다. 버튼 액션을 만들고 그안에 마찬가지로 present메소드를 이용한다 다른점이 있다면 스토리보드가 아닌 사파리를 넣는다.

import SafariServices

@IBAction func learnMoreTab(_ sender: Any) {
    guard let url = URL(string: framework.urlString)else{
        return
    }
    let safari = SFSafariViewController(url: url)
    present(safari, animated: true)
}

예제2

이번에는 네비게이션을 통해 유저 인터랙션을 컨트롤한다.

2행 섹션 두개

이번에는 콜렉션뷰를 좀 멋있게 커스텀 해본다.

//
//  QuickFocusListViewController.swift
//  HeadSpaceFocus
//
//  Created by 이은호 on 2023/02/22.
//

import UIKit

class QuickFocusListViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!

    let breathingList = QuickFocus.breathing
    let walkingList = QuickFocus.walking
    
    enum Section: CaseIterable{
        case breathe
        case walking
        
        var title: String{
            switch self{
            case .breathe:return "호흡에 집중하세요"
            case .walking:return "걸으면서 명상하세요"
            }
        }
    }
    typealias Item = QuickFocus
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    
        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
            }
            cell.configure(item)
            return cell
        })
        
        var snapshot = NSDiffableDataSourceSnapshot<Section,Item>()
        snapshot.appendSections([.breathe,.walking])
        snapshot.appendItems(walkingList,toSection: .walking)
        snapshot.appendItems(breathingList,toSection: .breathe)
        datasource.apply(snapshot)
        
        collectionView.collectionViewLayout = layout()
    }
    private func layout() -> UICollectionViewCompositionalLayout{
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem:item , count:2)
        let section = NSCollectionLayoutSection(group: group)
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
    
}

그리고 마찬가지로 present를 이용한다.

reusableView

콜렉션의 섹션마다 타이틀을 넣고 싶을때 사용한다. 참고로 커스텀 클래스가 필요하다.
QuickFocusHeaderView

import UIKit

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

그리고 스토리보드에 헤더뷰와 클래스를 연결한다. 그리고 다시 뷰컨으로 돌아오는데 이부분은 조금 복잡하다.

datasource.supplementaryViewProvider = {(collectionView,kind,indexPath) in
            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "QuickFocusHeaderView", for: indexPath) as? QuickFocusHeaderView else{
                return nil
            }
            let allSection = Section.allCases
            let section = allSection[indexPath.section]
            header.configure(section.title)
            return header
        }

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

방금전 만든 코드와 연결한후 레이아웃에 출력할수 있도록 작성해 주었다.

네비게이션

이제 모달뷰에 네비를 달아주자. 쥰내 쉽다.
1. 일단 각각 네비를 임베드 하고 각자 색깔(틴트컬러)이나 메인페이지의 타이틀을 커스텀한다.
2. 그리고 푸시하는 경우, 타이틀을 함께 보내주면 된다.
3. 이경우 부모뷰의 스타일을 따라가는데 코드를 통해 커스텀을 할 수 있다.
focusViewController

let vc = sb.instantiateViewController(withIdentifier: "QuickFocusListViewController") as! QuickFocusListViewController
//present(vc, animated: true)
vc.title = item.title
navigationController?.pushViewController(vc, animated: true)

quickFocusController

override func viewDidLoad() {
    self.navigationItem.largeTitleDisplayMode = .never
}

부록

아 왜 어시스턴트 안뜨는데

껏다키면댑니다

this class is not key value coding

여러가지 이유가 있다.
1. 아웃렛연결 확인
2. 식별자 다시 넣기 (넣어져 있어도 다시한번 지우고 넣어보자. 나는 이걸로 되었다;;)

회고

확장되는 앱의 경우, 무조건 한번 이상씩은 만들어 봐야겠다. 특히 프레임워크,명상,스포티파이는 다시한번 작업을 해야한다. 내 뇌가 그동안 잊어버릴테고, 잊어버리면서 복습을 통해 더 단단한 기반이 도리 수 있다.

0개의 댓글