많은 기능은 곧 많은 화면전환을 의미한다.
화면전환은 크게 네비게이션과 모달로 나눠진다.
모달은 모달뷰와 알람등이 있으며 일시적으로 유저의 집중을 요구하는 컨텐츠를 표시할때 사용한다.
애플에서 제공해주는 모달
present(_:animated:completion:)
메소드를 이용한다.네비게이션같은경우 인스타의 파도타기(?)등등에서 유저가 회복할 수 있도록 개발을 해야한다. 아키텍쳐같은 경우엔 모든 앱이 각자의 특성에 맞게 개발되기에 특정한 스타일을 굳이 명명하기는 어렵다.
주요원칙:
구현은 탭바컨트롤러와 네비게이션 컨트롤러를 이용한다.
모달은 집중, 네비게이션은 말그대로 경로와 위치를 잘 알려줘야한다.
예전에 만들어본 앱에서 모달을 뛰우고 사파리를 띄우는 어플로 업그레이드 한다. 먼저 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)
}
}
그러면 모달로 해당 데이터들을 전달해야만 한다. 어떻게 작성할까? 모달을 작성해주면서 전달하면 된다.
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)
}
이번에는 네비게이션을 통해 유저 인터랙션을 컨트롤한다.
이번에는 콜렉션뷰를 좀 멋있게 커스텀 해본다.
//
// 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를 이용한다.
콜렉션의 섹션마다 타이틀을 넣고 싶을때 사용한다. 참고로 커스텀 클래스가 필요하다.
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
}
껏다키면댑니다
여러가지 이유가 있다.
1. 아웃렛연결 확인
2. 식별자 다시 넣기 (넣어져 있어도 다시한번 지우고 넣어보자. 나는 이걸로 되었다;;)
확장되는 앱의 경우, 무조건 한번 이상씩은 만들어 봐야겠다. 특히 프레임워크,명상,스포티파이는 다시한번 작업을 해야한다. 내 뇌가 그동안 잊어버릴테고, 잊어버리면서 복습을 통해 더 단단한 기반이 도리 수 있다.