728x90
반응형

[Review] (5주차) 실습

2022.07.16 - [Dev/Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.05

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.05

[Review] (4주차) 실습 2022.07.10 - [Dev/Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.04 [Xcode] iOS Swift 앱 개발 Byte Degree - week.04 [Review] (3주차) 실습 2022.07.01 - [Swift] - [Xcode]..

sarahee.tistory.com


최종 구현

 

그리드 레이아웃 변경 작업

FocusViewController 및 QuickFocusListViewController의  레이아웃 작업 변경(UICollectionViewCompositionalLayout 응용)

 

FocusViewController code 기존 수정 해설
item의 .fractionalWidth(1) 1 0.9 크기 조절
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item]) vertical horizontal 스크롤 방향: 세로 → 가로
section.orthogonalScrollingBehavior = .groupPagingCentered   신규 추가 스크롤 시 item 중앙 정렬
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20) leading: 20, trailing: 20 leading: 5, trailing: 5 좌우 padding 줄이기
group의 .fractionalWidth(1) 1 0.9 item과 동일하게 설정(group padding이 별도로 설정되지 않도록)
section.interGroupSpacing = 10 10 -25 좌우 여백값 조절

 

QuickFocusListViewController code 기존 수정 해설
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2) subitem: item, count: 2 subitems: [item] 동적으로 할당하도록 개수  설정 해제
section.orthogonalScrollingBehavior = .continuous   신규 추가 스크롤 시 item 정렬되지 않도록(이동한 만큼 위치)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
subitems: [item] subitem: item, count: 1 변경사항 복구
section.interGroupSpacing = 20 20 10 좌우 padding 줄이기

QuickFocusListViewController의 UIViewController에서

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
        // 기존
//        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
        // 변경: 유연하게 할당될 수 있도록
		let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        group.interItemSpacing = .fixed(10)
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 20, bottom: 30, trailing: 20)
        section.interGroupSpacing = 20
        // 추가: .continuous, .groupPagingCentered
        section.orthogonalScrollingBehavior = .continuous

Collection View Cell

수평적으로 화면 조회되지만, 각 화면에 하나의 group만 존재하도록 설정됨

(참고) corner의 값 조절 - 코드에 반영하지 않음

collectionView.collectionViewLayout = layout()
collectionView.delegate = self
// 추가: width의 값이 동적으로 조절
collectionView.clipsToBounds = true

→ group의 fractionalWidth도 item과 동일하게 설정해 주어야 함 + spacing 값 또한 조절


UICollectionViewCompositionalLayout

출처:  https://developer.apple.com/documentation/uikit/uicollectionviewcompositionallayout

func createBasicListLayout() -> UICollectionViewLayout { 
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),                                  
                                         heightDimension: .fractionalHeight(1.0))    
    let item = NSCollectionLayoutItem(layoutSize: itemSize)  
  
    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),                                          
                                          heightDimension: .absolute(44))    
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,                                                   
                                                     subitems: [item])  
  
    let section = NSCollectionLayoutSection(group: group)    

    let layout = UICollectionViewCompositionalLayout(section: section)    
    return layout
}

group.interItemSpacing에 int 값을 넣으면 다음과 같은 오류 발생

Cannot assign value of type 'int' to type 'NSCollectionLayoutSpacing?'

 

group size의 fractionWidth를 item size와 동일하게 0.5로 설정할 경우 다음과 같은 화면 조회

두 item처럼 보이는 부분이 하나의 item으로 설정됨

QuickFocusCell의 count를 subitems: [item] → subitem: item, count: 1 설정

좌측부터 차례대로 FocusViewController, QuickFocusListViewController

최종 코드

//
//  FocusViewController.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/10.
//

import UIKit

class FocusViewController: UIViewController {
    
    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var refreshButton: UIButton!
    
    var curated: Bool = false
    var items: [Focus] = Focus.list
    
    typealias Item = Focus
    enum Section {
        case main
    }
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        refreshButton.layer.cornerRadius = 10
        
        // Presentation: Diffable Datasource + Cell Provider
        datasource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FocusCell", for: indexPath) as? FocusCell else {
                return nil
            }
            cell.configure(item)
            return cell
        })
        
        // Data: Snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        
        // Layout
        collectionView.collectionViewLayout = layout()
        collectionView.delegate = self
        
        updateButtonTitle()
    }
    
    private func layout() -> UICollectionViewCompositionalLayout {
        
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .estimated(50))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .estimated(50))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 5, bottom: 10, trailing: 5)
        section.interGroupSpacing = -25
        section.orthogonalScrollingBehavior = .groupPagingCentered
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
    
    func updateButtonTitle() {
        let title = curated ? "See All" : "See Recommendation"
        refreshButton.setTitle(title, for: .normal)
    }
    
    @IBAction func refreshButtonTapped(_ sender: Any) {
        curated.toggle()
        
        self.items = curated ? Focus.recommendations : Focus.list
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        
        updateButtonTitle()
    }
}

extension FocusViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let item = items[indexPath.item]
//        print(">>> \(item.title)")
        
        let storyboard = UIStoryboard(name: "QuickFocus", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "QuickFocusListViewController") as! QuickFocusListViewController
        vc.title = item.title
//        present(vc, animated: true)
        navigationController?.pushViewController(vc, animated: true)
    }
}
//
//  QuickFocusListViewController.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/21.
//

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 "Breathing exercises"
            case .walking: return "Mindful walks"
            }
        }
    }
    
    typealias Item = QuickFocus
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // enum Section CaseIterable 생성한 코드가 다음 내용과 동일
//        Section.allCases
//        let allItems: [Section] = [.breathe, .walking]
        // 생성 코드가 다음과 같이 쓰일 수 있음
//        let section: Section = .breathe
//        section.title
        
        // 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
            }
            cell.configure(item)
            return cell
        })
        
        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
        }
        
        // Data
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.breathe, .walking])
        // 위의 코드와 동일
//        snapshot.appendSections(Section.allCases)
        snapshot.appendItems(breathingList, toSection: .breathe)
        snapshot.appendItems(walkingList, toSection: .walking)
        datasource.apply(snapshot)
        
        // Layout
        collectionView.collectionViewLayout = layout()
        self.navigationItem.largeTitleDisplayMode = .never
    }
    private func layout() -> UICollectionViewCompositionalLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(50))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(50))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
        group.interItemSpacing = .fixed(10)
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 20, bottom: 30, trailing: 20)
        section.interGroupSpacing = 10
        section.orthogonalScrollingBehavior = .continuous
        
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50))
        let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
        section.boundarySupplementaryItems = [header]
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
}

 

 

728x90
728x90
728x90
반응형

[Review] (4주차) 실습

2022.07.10 - [Dev/Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.04

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.04

[Review] (3주차) 실습 2022.07.01 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.03 [Xcode] iOS Swift 앱 개발 Byte Degree - week.03 [Review] (2주차) 실습 2022.06.26 - [Swift] - [Xcode] iOS..

sarahee.tistory.com


애플 프레임워크 모달 - 상세뷰로 진입하거나, 새로운 뷰 띄워보기

// 위임 self
override func viewOldLoad() {
	collectionView.delegate = self
}

extension FrameworkListViewController: UICollectionViewDelegate {
	func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    	let framework = list[indexPath.item]
        print(">>> selected: \(framework.name)")
        
        // FrameworkDetailViewController 띄우고자 함
        let storyboard = UIStoryboard(name: "Detail", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "FrameworkDetailViewController")
        	as! FrameworkDetailViewController
        vc.framework = framework
        present(vc, animated: true)
    }
}

멀티라인으로 설정하기 위해서는..

Label의 Lines: 1 → 0

 

Combine Overview

주요 컴포넌트 3가지

Publisher: 생산자, 크리에이터

Subscriber: 소비자, 구독자

Operator: 변경시키는 사람, 가공하는 사람

-

실습

Class: QuickFocusListViewController, Subclass of: UIViewController 추가

Class: QuickFocusCell, Subclass of: UICollectionViewCell 추가

//
//  QuickFocusListViewController.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/21.
//

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 "Breathing exercises"
            case .walking: return "Mindful walks"
            }
        }
    }
    
    typealias Item = QuickFocus
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // enum Section CaseIterable 생성한 코드가 다음 내용과 동일
//        Section.allCases
//        let allItems: [Section] = [.breathe, .walking]
        // 생성 코드가 다음과 같이 쓰일 수 있음
//        let section: Section = .breathe
//        section.title
        
        // 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
            }
            cell.configure(item)
            return cell
        })
        
        // Data
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.breathe, .walking])
        // 위의 코드와 동일
//        snapshot.appendSections(Section.allCases)
        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)
        
        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
    }
}

코드를 연결하기 위해, FocusViewController의 Layout에 다음 코드 추가

collectionView.delegate = self

하단 코드 연결

extension FocusViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let item = items[indexPath.item]
        print(">>> \(item.title)")
        
        let storyboard = UIStoryboard(name: "QuickFocus", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "QuickFocusListViewController") as! QuickFocusListViewController
        present(vc, animated: true)
    }
}

좌우 padding

1) section.contentInsets

2) section.interGroupSpacing, group.interItemSpacing

private func layout() -> UICollectionViewCompositionalLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), 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)
        group.interItemSpacing = .fixed(10)
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 20, bottom: 30, trailing: 20)
        section.interGroupSpacing = 20
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }

헤더 추가

+ > Collection Reusable View

해당 View의 custom class 생성 - Class: QuickFocusListHeaderView, Subclass of: UICollectionReusableView 추가

//
//  QuickFocusHeaderView.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/22.
//

import UIKit

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

QuickFocusListViewController 코드 삽입

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
        }

QuickFocusListViewController 하단 UICollectionViewCompositionalLayout

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

Focus View Controller UI 클릭 후, Editor > Embed in > Navigation Controller

FocusViewController의 UICollectionViewDelegate

// 기존
		present(vc, animated: true)
// 변경: Navigation - Back
		navigationController?.pushViewController(vc, animated: true)

Navigation Bar의 ☑ Prefers Large Titles, Title 이름은 Focus로 설정

FocusViewController의 UICollectionViewDelegate 코드 추가

vc.title = item.title

QuickFocusListViewController의 Layout 코드 추가

self.navigationItem.largeTitleDisplayMode = .never

Navigation Bar의 Tint color: Default에서 White Color로 변경

최종 코드

//
//  QuickFocusListViewController.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/21.
//

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 "Breathing exercises"
            case .walking: return "Mindful walks"
            }
        }
    }
    
    typealias Item = QuickFocus
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // enum Section CaseIterable 생성한 코드가 다음 내용과 동일
//        Section.allCases
//        let allItems: [Section] = [.breathe, .walking]
        // 생성 코드가 다음과 같이 쓰일 수 있음
//        let section: Section = .breathe
//        section.title
        
        // 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
            }
            cell.configure(item)
            return cell
        })
        
        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
        }
        
        // Data
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.breathe, .walking])
        // 위의 코드와 동일
//        snapshot.appendSections(Section.allCases)
        snapshot.appendItems(breathingList, toSection: .breathe)
        snapshot.appendItems(walkingList, toSection: .walking)
        datasource.apply(snapshot)
        
        // Layout
        collectionView.collectionViewLayout = layout()
        self.navigationItem.largeTitleDisplayMode = .never
    }
    private func layout() -> UICollectionViewCompositionalLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), 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)
        group.interItemSpacing = .fixed(10)
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 20, bottom: 30, trailing: 20)
        section.interGroupSpacing = 20
        
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50))
        let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
        section.boundarySupplementaryItems = [header]
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
}
//
//  QuickFocusCell.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/21.
//

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
    }
}
//
//  FocusViewController.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/10.
//

import UIKit

class FocusViewController: UIViewController {
    
    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var refreshButton: UIButton!
    
    var curated: Bool = false
    var items: [Focus] = Focus.list
    
    typealias Item = Focus
    enum Section {
        case main
    }
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        refreshButton.layer.cornerRadius = 10
        
        // Presentation: Diffable Datasource + Cell Provider
        datasource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FocusCell", for: indexPath) as? FocusCell else {
                return nil
            }
            cell.configure(item)
            return cell
        })
        
        // Data: Snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        
        // Layout
        collectionView.collectionViewLayout = layout()
        collectionView.delegate = self
        
        updateButtonTitle()
    }
    
    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.vertical(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)
        section.interGroupSpacing = 10
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
    
    func updateButtonTitle() {
        let title = curated ? "See All" : "See Recommendation"
        refreshButton.setTitle(title, for: .normal)
    }
    
    @IBAction func refreshButtonTapped(_ sender: Any) {
        curated.toggle()
        
        self.items = curated ? Focus.recommendations : Focus.list
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        
        updateButtonTitle()
    }
}

extension FocusViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let item = items[indexPath.item]
        print(">>> \(item.title)")
        
        let storyboard = UIStoryboard(name: "QuickFocus", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "QuickFocusListViewController") as! QuickFocusListViewController
        vc.title = item.title
//        present(vc, animated: true)
        navigationController?.pushViewController(vc, animated: true)
    }
}

[Next] (6주차) 실습

2022.07.22 - [Development/Swift] - [Swift] iOS 앱 개발(Xcode) Byte Degree - week.06

 

[Swift] iOS 앱 개발(Xcode) Byte Degree - week.06

[Review] (5주차) 실습 2022.07.16 - [Dev/Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.05 [Xcode] iOS Swift 앱 개발 Byte Degree - week.05 [Review] (4주차) 실습 2022.07.10 - [Dev/Swift] - [Xco..

sarahee.tistory.com

 

728x90
728x90
728x90
반응형

[Review] (3주차) 실습

2022.07.01 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.03

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.03

[Review] (2주차) 실습 2022.06.26 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.02 [Xcode] iOS Swift 앱 개발 Byte Degree - week.02 iOS 아키텍처 패턴 중 애플에서는 기본적으로 MVC 패턴을 가..

sarahee.tistory.com


다크모드 변경

command + shift + a (toggle)



주석 달기

command + /

 

내비게이션 컨트롤러 설정

Editor > Embed in > Navigation Controller

Prefers Large Titles 체크

 

Safe Area 설정

Vertical에서

Align Bottom to: Safe Area / Align Top to: Safe Area 더블클릭

Fitst Item: Safe Area Bottom -> Superview, 다시 이전 Edit -> Constant: 0 설정

 

좌우측 여백(padding)

override func viewDidLead() { 내에

collectionView.contentInset = UIEdgeInsets(top: 20, left: 30, bottom: 0, right: 30)


탭바 컨트롤러의 주요 뷰

[Documentation] UITabBarController

https://developer.apple.com/documentation/uikit/uitabbarcontroller

 

File > New > File (or command + N) > Cocoa Touch Class > Class: FocusViewController, Subclass of: UIViewController

Main > Focus View Controller의 Class와 Storyboard ID에 FocusViewController 입력

 

UI의 Collection View 설정, 셀 디자인 및 오토레이아웃 설정

//
//  FocusViewController.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/10.
//

import UIKit

class FocusViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!
    var items: [Focus] = Focus.list
    typealias Item = Focus
    enum Section {
        case main
    }
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Presentation, Data, Layout
        datasource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FocusCell", for: indexPath) as? FocusCell else {
                return nil
            }
            cell.configure(item)
            return cell
        })
        // data: Snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        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.vertical(layoutSize: groupSize, subitems: [item])
        let section = NSCollectionLayoutSection(group: group)
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
}
//
//  FocusCell.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/10.
//

import UIKit

class FocusCell: UICollectionViewCell {
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var thumbnailImageView: UIImageView!
    
    func configure(_ item: Focus) {
        titleLabel.text = item.title
        descriptionLabel.text = item.description
        thumbnailImageView.image = UIImage(systemName: item.imageName)?.withRenderingMode(.alwaysOriginal)
    }
}

FocusCell 코드 추가

override func awakeFromNib() {
        super.awakeFromNib()
        contentView.backgroundColor = UIColor.systemIndigo
        contentView.layer.cornerRadius = 10
    }

FocusViewController 코드 추가

let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)
        section.interGroupSpacing = 10

FocusViewController 코드 추가

// 1
@IBOutlet weak var refreshButton: UIButton!
    var curated: Bool = false

// 2
let title = curated ? "See All" : "See Recommendation"
        refreshButton.setTitle(title, for: .normal)
    
// 3
@IBAction func refreshButtonTapped(_ sender: Any) {
        curated.toggle()
        self.items = curated ? Focus.recommendations : Focus.list
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        
        let title = curated ? "See All" : "See Recommendation"
        refreshButton.setTitle(title, for: .normal)
    }

DRY 원칙(Do not Repeat Yourself)

updateButtonTitle() 함수 생성하여 중복 코드 제거

func updateButtonTitle() {
        let title = curated ? "See All" : "See Recommendation"
        refreshButton.setTitle(title, for: .normal)
    }

버튼 둥글게 설정

refreshButton.layer.cornerRadius = 10

전체 코드

//
//  FocusViewController.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/10.
//

import UIKit

class FocusViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!
    
    @IBOutlet weak var refreshButton: UIButton!
    
    var curated: Bool = false
    var items: [Focus] = Focus.list
    
    typealias Item = Focus
    enum Section {
        case main
    }
    
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        refreshButton.layer.cornerRadius = 10
        
        // Presentation, Data, Layout
        datasource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FocusCell", for: indexPath) as? FocusCell else {
                return nil
            }
//            let data = self.items[indexPath.item]
//            cell.configure(data)
            cell.configure(item)
            return cell
        })
        // data: Snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        collectionView.collectionViewLayout = layout()
        
        updateButtonTitle()
    }
    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.vertical(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)
        section.interGroupSpacing = 10
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
    
    func updateButtonTitle() {
        let title = curated ? "See All" : "See Recommendation"
        refreshButton.setTitle(title, for: .normal)
    }
    
    @IBAction func refreshButtonTapped(_ sender: Any) {
        curated.toggle()
        self.items = curated ? Focus.recommendations : Focus.list
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items, toSection: .main)
        datasource.apply(snapshot)
        
        updateButtonTitle()
    }
}
//
//  FocusCell.swift
//  HeadSpaceFocus
//
//  Created by sehee on 2022/07/10.
//

import UIKit

class FocusCell: UICollectionViewCell {
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var thumbnailImageView: UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        contentView.backgroundColor = UIColor.systemIndigo
        contentView.layer.cornerRadius = 10
    }
    
    func configure(_ item: Focus) {
        titleLabel.text = item.title
        descriptionLabel.text = item.description
        thumbnailImageView.image = UIImage(systemName: item.imageName)?.withRenderingMode(.alwaysOriginal)
    }
}

[Next] (5주차) 실습

2022.07.16 - [Dev/Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.05

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.05

[Review] (4주차) 실습 2022.07.10 - [Dev/Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.04 [Xcode] iOS Swift 앱 개발 Byte Degree - week.04 [Review] (3주차) 실습 2022.07.01 - [Swift] - [Xcode]..

sarahee.tistory.com

 

728x90
728x90
728x90
반응형

[Review] (2주차) 실습

2022.06.26 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.02

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.02

iOS 아키텍처 패턴 중 애플에서는 기본적으로 MVC 패턴을 가이드함 Product Name: HelloiOS Organization Identifier: com.sehee Interface: Storyboard (other options: SwiftUI) Language: Swift (other option..

sarahee.tistory.com


Intro

Swift의 기본 기능, 리스트 및 그리드에 대해 알아보자

Xcode 실행시 기본 스토리보드

File > New > File (or command + n)

view controller 추가하기

Cocoa Touch Class > (Choose options for your new file) Class: StockRankViewController > Next > Create

좌측 Main Tab의 View Controller > 우측 Custom Class - Class 및 Identity - Storyboard ID: StockRankViewController 설정

* Storyboard ID에서 검색이 용이하도록(class 이름 자체가 unique한 id이므로 동일하게 설정해도 무방)

 

Main storyboard에서 우측 상단 + 버튼 클릭하여 UI components(objects) 선택

Collection View 추가

 


Auto rayout 설정

자식 뷰(Collection View)를 부모 뷰(View)에 연관되게 설정하기 위함

control 버튼을 누른 상태에서 Collection View → View 드래그

Shift를 누른 상태에서 클릭하여 다중 선택(상단 네개 Space to Safe Area 모두 클릭) 이후 Enter

View를 Code로 연결하기

해당하는 스토리보드(Collection View) 클릭 후 우측 상단 =(리스트) 아이콘 클릭

Assistant 클릭하여 새로운 view 생성

control 누른 상태에서 연결하고 싶은 뷰(Collection View) → 코드 드래그

name: collectionView > connect

import UIKit

class StockRankViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}

Connection View Cell 내에 Label, Image View 추가 (option 누르고 드래그할 경우 복제)

Image View는 우측 Image View > Image에서 검색하여 변경 (e.g. heart.fill)

적용: option + command + =

//
//  StockRankViewController.swift
//  StockRank
//
//  Created by sehee on 2022/06/30.
//

import UIKit

class StockRankViewController: UIViewController {

    let stockList: [StockModel] = StockModel.list
    @IBOutlet weak var collectionView: UICollectionView!
    
    // Data, Presentation, Layout
    // Data: 어떤 떼이터? - stockList
    // Presentation: 셀을 어떻게 표현?
    // Layout: 셀을 어떻게 배치?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.dataSource = self
        collectionView.delegate = self
    }
}

extension StockRankViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return stockList.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StockRankCollectionViewCell", for: indexPath)
        return cell
        // return UICollectionViewCell()
    }
}

extension StockRankViewController: UICollectionViewDelegateFlowLayout {
    }
}

extension StockRankViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // width == collectionView
        return CGSize(width: collectionView.bounds.width, height: 80)
    }
}

StockRankCollectionViewCell code - data update

//
//  StockRankCollectionViewCell.swift
//  StockRank
//
//  Created by sehee on 2022/07/01.
//

import UIKit

class StockRankCollectionViewCell: UICollectionViewCell {
    // uicomponent 연결하자
    // uicomponent에 데이터 업데이트 하는 코드를 넣자
    @IBOutlet weak var rankLabel: UILabel!
    @IBOutlet weak var companyIconImageView: UIImageView!
    @IBOutlet weak var companyNameLabel: UILabel!
    @IBOutlet weak var companyPriceLabel: UILabel!
    @IBOutlet weak var diffLabel: UILabel!
    
    func configure(_ stock: StockModel) {
        rankLabel.text = "\(stock.rank)"
        companyIconImageView.image = UIImage(named: stock.imageName)
        companyNameLabel.text = stock.name
        companyPriceLabel.text = "\(stock.price) 원"
        diffLabel.text = "\(stock.diff)%"
    }
}

StockRankViewController code - data update

//
//  StockRankViewController.swift
//  StockRank
//
//  Created by sehee on 2022/06/30.
//

import UIKit

class StockRankViewController: UIViewController {

    let stockList: [StockModel] = StockModel.list
    @IBOutlet weak var collectionView: UICollectionView!
    
    // Data, Presentation, Layout
    // Data: 어떤 떼이터? - stockList
    // Presentation: 셀을 어떻게 표현?
    // Layout: 셀을 어떻게 배치?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.dataSource = self
        collectionView.delegate = self
    }
}

extension StockRankViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return stockList.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        // guard: as? (꼭 참이어야 하는 조건) else { return (거짓일 경우) } (참일 경우)
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StockRankCollectionViewCell", for: indexPath) as? StockRankCollectionViewCell else {
            return UICollectionViewCell()
        }
        let stock = stockList[indexPath.item]
        cell.configure(stock)
        return cell
    }
}

extension StockRankViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // width == collectionView
        return CGSize(width: collectionView.bounds.width, height: 80)
    }
}

가격 표현(쉼표 추가)

//
//  StockRankCollectionViewCell.swift
//  StockRank
//
//  Created by sehee on 2022/07/01.
//

import UIKit

class StockRankCollectionViewCell: UICollectionViewCell {
    // uicomponent 연결하자
    // uicomponent에 데이터 업데이트 하는 코드를 넣자
    @IBOutlet weak var rankLabel: UILabel!
    @IBOutlet weak var companyIconImageView: UIImageView!
    @IBOutlet weak var companyNameLabel: UILabel!
    @IBOutlet weak var companyPriceLabel: UILabel!
    @IBOutlet weak var diffLabel: UILabel!
    
    func configure(_ stock: StockModel) {
        rankLabel.text = "\(stock.rank)"
        companyIconImageView.image = UIImage(named: stock.imageName)
        companyNameLabel.text = stock.name
        companyPriceLabel.text = "\(convertToCurrencyFormat(price: stock.price)) 원"
        diffLabel.text = "\(stock.diff)%"
    }

    func convertToCurrencyFormat(price: Int) -> String {
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        numberFormatter.maximumFractionDigits = 0
        let result = numberFormatter.string(from: NSNumber(value: price)) ?? ""
        return result
    }
}

(추가) 사용자 UI 반영 - 마이너스일 때 파란색 표시, 플러스일 때는 기본 빨간색 표시

# sol1

        var color: UIColor
        if stock.diff > 0 {
            color = UIColor.systemRed
        } else {
            color = UIColor.systemBlue
        }
        diffLabel.textColor = color

# sol2

        diffLabel.textColor = stock.diff > 0 ? UIColor.systemRed : UIColor.systemBlue

최종 코드

//
//  StockRankViewController.swift
//  StockRank
//
//  Created by sehee on 2022/06/30.
//

import UIKit

class StockRankViewController: UIViewController {

    let stockList: [StockModel] = StockModel.list
    @IBOutlet weak var collectionView: UICollectionView!
    
    // Data, Presentation, Layout
    // Data: 어떤 떼이터? - stockList
    // Presentation: 셀을 어떻게 표현?
    // Layout: 셀을 어떻게 배치?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionView.dataSource = self
        collectionView.delegate = self
    }
}

extension StockRankViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return stockList.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        // guard: as? (꼭 참이어야 하는 조건) else { return (거짓일 경우) } (참일 경우)
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StockRankCollectionViewCell", for: indexPath) as? StockRankCollectionViewCell else {
            return UICollectionViewCell()
        }
        let stock = stockList[indexPath.item]
        cell.configure(stock)
        return cell
    }
}

extension StockRankViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // width == collectionView
        return CGSize(width: collectionView.bounds.width, height: 80)
    }
}
//
//  StockRankCollectionViewCell.swift
//  StockRank
//
//  Created by sehee on 2022/07/01.
//

import UIKit

class StockRankCollectionViewCell: UICollectionViewCell {
    // uicomponent 연결하자
    // uicomponent에 데이터 업데이트 하는 코드를 넣자
    @IBOutlet weak var rankLabel: UILabel!
    @IBOutlet weak var companyIconImageView: UIImageView!
    @IBOutlet weak var companyNameLabel: UILabel!
    @IBOutlet weak var companyPriceLabel: UILabel!
    @IBOutlet weak var diffLabel: UILabel!
    
    func configure(_ stock: StockModel) {
        rankLabel.text = "\(stock.rank)"
        companyIconImageView.image = UIImage(named: stock.imageName)
        companyNameLabel.text = stock.name
        companyPriceLabel.text = "\(convertToCurrencyFormat(price: stock.price)) 원"
/*        var color: UIColor
        if stock.diff > 0 {
            color = UIColor.systemRed
        } else {
            color = UIColor.systemBlue
        }
        diffLabel.textColor = color
 */
        diffLabel.textColor = stock.diff > 0 ? UIColor.systemRed : UIColor.systemBlue
        diffLabel.text = "\(stock.diff)%"
    }

    func convertToCurrencyFormat(price: Int) -> String {
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        numberFormatter.maximumFractionDigits = 0
        let result = numberFormatter.string(from: NSNumber(value: price)) ?? ""
        return result
    }
}

기본 제공 코드

//
//  StockModel.swift
//  StockRank
//
//  Created by joonwon lee on 2022/04/19.
//

import Foundation

struct StockModel {
    let rank: Int
    let imageName: String
    let name: String
    let price: Int
    let diff: Double
}

extension StockModel {
    static let list: [StockModel] = [
        StockModel(rank: 1, imageName: "TSLA", name: "테슬라", price: 1_238_631, diff: 0.04),
        StockModel(rank: 2, imageName: "AAPL", name: "애플", price: 238_631, diff: 1.04),
        StockModel(rank: 3, imageName: "NFLX", name: "넷플릭스", price: 438_631, diff: -0.04),
        StockModel(rank: 4, imageName: "GOOG", name: "알파벳 A", price: 3_176_631, diff: 0.04),
        StockModel(rank: 5, imageName: "AMZN", name: "아마존", price: 3_538_631, diff: 0.04),
        StockModel(rank: 6, imageName: "NIKE", name: "나이키", price: 158_631, diff: 0.04),
        StockModel(rank: 7, imageName: "DIS", name: "디즈니", price: 138_631, diff: 0.04),
        StockModel(rank: 8, imageName: "TSLA", name: "테슬라", price: 1_238_631, diff: 0.04),
        StockModel(rank: 9, imageName: "AAPL", name: "애플", price: 238_631, diff: 1.04),
        StockModel(rank: 10, imageName: "NFLX", name: "넷플릭스", price: 438_631, diff: -0.04),
        StockModel(rank: 11, imageName: "GOOG", name: "알파벳 A", price: 3_176_631, diff: 0.04),
        StockModel(rank: 12, imageName: "AMZN", name: "아마존", price: 3_538_631, diff: 0.04),
        StockModel(rank: 13, imageName: "NIKE", name: "나이키", price: 158_631, diff: 0.04),
        StockModel(rank: 14, imageName: "DIS", name: "디즈니", price: 138_631, diff: 0.04),
    ]
}

[Next] (4주차) 실습

2022.07.10 - [Dev/Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.04

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.04

[Review] (3주차) 실습 2022.07.01 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.03 [Xcode] iOS Swift 앱 개발 Byte Degree - week.03 [Review] (2주차) 실습 2022.06.26 - [Swift] - [Xcode] iOS..

sarahee.tistory.com

 

728x90
728x90
728x90
반응형

[Review] (1주차) 실습

2022.06.19 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.01

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.01

[강의노트] 왕초보를 위한, 한 번에 끝내는 iOS 앱 개발 바이블 https://opposite-foundation-5b5.notion.site/iOS-4b1c2a2c1baf4ae29971524e9bdfc074 [Github] https://github.com/cafielo/fc-ios-bible-for-begi..

sarahee.tistory.com


iOS 아키텍처 패턴 중 애플에서는 기본적으로 MVC 패턴을 가이드함

SF Symbols download

Product Name: HelloiOS

Organization Identifier: com.sehee

Interface: Storyboard (other options: SwiftUI)

Language:  Swift (other options: Objective-C)

command + shift + C

글자 복사

기존 프레임 기반 레이아웃 → 오토레이아웃: 동적으로 뷰 위치

//
//  SymbolRollerViewController.swift
//  SymbolRoller
//
//  Created by sehee on 2022/06/26.
//

import UIKit

class SymbolRollerViewController: UIViewController {

    let symbols: [String] = ["sun.min", "moon", "cloud", "wind", "snowflake"]
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var button: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
//        button.tintColor = UIColor.systemPink
        // TO-DO:
        // - 심볼에서, 하나를 임의로 추출해서
        // 이미지와 텍스트를 설정
        // DRY: Do not repeat yourself
        reload()
        // Do any additional setup after loading the view.
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
    func reload() {
        let symbol = symbols.randomElement()!
        imageView.image = UIImage(systemName: symbol)
        label.text = symbol
    }
    
    @IBAction func buttonTapped(_ sender: Any) {
        reload()
    }
    
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}

ViewController: 페이지를 나타내기 위한 객체

option + shift + 8

° 기호

option + command + =

설정값 변경 전(주황색 표시) 업데이트

//
//  WeatherViewController.swift
//  SimpleWeather
//
//  Created by sehee on 2022/06/26.
//

import UIKit

class WeatherViewController: UIViewController {

    @IBOutlet weak var cityLabel: UILabel!
    @IBOutlet weak var weatherImageView: UIImageView!
    @IBOutlet weak var temperatureLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    let cities = ["Seoul", "Tokyo", "LA", "Seattle"]
    let weathers = ["cloud.fill", "sun.max.fill", "wind", "cloud.sun.rain.fill"]
    
    @IBAction func changeeButtonTapped(_ sender: Any) {
        print("도시, 온도, 날씨 이미지 변경하자!")
        cityLabel.text = cities.randomElement()
        let imageName = weathers.randomElement()!
        weatherImageView.image = UIImage(systemName: imageName)?.withRenderingMode(.alwaysOriginal)
        // alwaysTemplate의 경우 default Tint color 적용
        let randomTemp = Int.random(in: 10..<30)
        temperatureLabel.text = "\(randomTemp)°"
    }
    
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}

 


[Next] (3주차) 실습

2022.07.01 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.03

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.03

[Review] (2주차) 실습 2022.06.26 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.02 [Xcode] iOS Swift 앱 개발 Byte Degree - week.02 iOS 아키텍처 패턴 중 애플에서는 기본적으로 MVC 패턴을 가..

sarahee.tistory.com

 

 

728x90
728x90
728x90
반응형

[강의노트] 왕초보를 위한, 한 번에 끝내는 iOS 앱 개발 바이블

https://opposite-foundation-5b5.notion.site/iOS-4b1c2a2c1baf4ae29971524e9bdfc074

[Github]

https://github.com/cafielo/fc-ios-bible-for-beginner-2022


Create a new Xcode project > File > New > Playground

iOS - Blank > Next > Save As: HelloSwift


01. 변수, 상수

command + /: 주석 변경

변수/상수에 커서 + option (누르면 ? 표시) + 클릭하면 타입 정보 조회 가능

import UIKit

var greeting = "Hello, playground"

// ---------- 01. 변수, 상수 ----------
// var: 변수(variable) 선언
var num = 5
num = 1

// let: 상수(constant) 선언(값 수정 불가)
var name = "Jason"
name = "Jay"

// String
let quote = "I'm mother father genius, Man"

// Integer
let num2 = 5
let otherNum = num2 + 2         // 7
let halfOfNum = otherNum / 2    // 3

// 내장함수
let num3 = 360
// 3의 배수인지 확인하는 코드
print(num3.isMultiple(of: 3))
// 0~300 사이중 랜덤으로 수를 뽑아내는 메소드
let random = Int.random(in: 0...300)

// Double: 소수점을 포함하는 숫자를 표현
let score = 3.6

 

02. bool-array

// ---------- 02. bool-array ----------
// Boolean
let isMan = true
let isHuman = false

var isSingle = true
isSingle.toggle()
isSingle

// String Interpolation
let difficulty = "쉽다"
let maximumAge = 80

let message = "\(maximumAge) 할머니도 배우는 iOS 개발은 \(difficulty)"

// 배열과 딕셔너리
// Array
let ages = [3, 20, 60]
let colors = ["green", "red", "yellow"]
let color = colors[0]

// Dictionary (Key: Value)
let languageCode = [
    "한국" : "ko",
    "미국" : "en",
    "일본" : "ja",
]

// 비어 있는 배열과 딕셔너리 초기화
var emptyArr: [Int] = []
var emptyDic: [String: Any] = [:]

 

03. enum

// ---------- 03. enum ----------
// 서로 관계있는 값들을 모아서 표현한 것
enum WeekDay {
    case mon
    case tue
    case wed
    case thu
    case fri
}
var today: WeekDay = .mon
// var tod: String = "mon"      // error(mom)시 찾기 힘듦

enum MediaType {
    case audio
    case video
}
var mediaType: MediaType = .audio

// 위에서 만들어본 미디어 타입에, 파일 확장자도 문자열로 받을 수 있게 수정
enum MediaType2 {
    case audio(String)
    case video(String)
}
var mp3: MediaType2 = .audio("mp3")
var h264: MediaType2 = .video("h264")

 

04. 조건문, 반복문

// ---------- 04. 조건문, 반복문 ----------
let age = 10
// if문의 조건절에는 boolean 타입을 사용 (e.g. age > 20)
if age > 20 {
    print("성인 입니다")
} else {
    print("미성년 입니다")
}

if age >= 10 && age < 20 {
    print("10대 입니다")
} else if age >= 20 && age < 30 {
    print("20대 입니다")
} else if age >= 30 && age < 40 {
    print("30대 입니다")
} else if age >= 40 && age < 50 {
    print("40대 입니다")
} else {
    print("......")
}

enum Weather {
    case sun
    case cloud
    case rain
}

var weather: Weather = .sun
switch weather {
case .sun:
    print("맑아요")
case .cloud:
    print("흐려요")
case .rain:
    print("비와요")
}

// 배열과 딕셔너리 아이템을 순차적으로 체크할 때 사용
let ages2 = [3, 20, 60]
let languageCode2 = [
    "한국" : "ko",
    "미국" : "en",
    "일본" : "ja",
]
for age in ages2 {
    print("age: \(age)")
}
for (key, value) in languageCode2 {
    print("\(key)의 언어코드는 \(value)")
}

// 일정 횟수를 단순 반복
print("전방에 다짐 10번 발사~~!")
for _ in 0..<10 {
    print("나는 iOS 개발자다!")
}

// while: 특정 조건을 이용해서 반복
var count = 10
print("Ready!")
while count > 0 {
    print("\(count)...")
    count -= 1
}
print("START!")

 

05. 옵셔널

// ---------- 05. 옵셔널 ----------
// 값이 있을수도 있고 없을수도 있음을 표현
// Dictionary (Key: Value)
let languageCode3 = [
    "한국" : "ko",
    "미국" : "en",
    "일본" : "ja",
]
// let 상수 타입은 String? (타입 + ?)
let krCode = languageCode3["한국"]    // "ko"
let jpCode = languageCode3["일본"]    // "ja"
let deCode = languageCode3["독일"]    // nil

// 이름이 있을 수도 있고 없을 수도 있는 타입 선언 - String?
var name2: String? = nil
name2 = "Jason"
name2 = nil

 

06. 함수, 클로저

// ---------- 06. 함수, 클로저 ----------
// 함수는 func 키워드를 사용해서 선언
func printGugu(dan: Int) {
    for i in 1...9 {
        print("\(dan) * \(i) = \(dan * i)")
    }
}
printGugu(dan: 5)

func rollDice() -> Int {
    return Int.random(in: 1...6)
}
let random2 = rollDice()

// 클로저: 이름이 존재하지 않는 함수
// 함수
func call(name: String) {
    print("hello, \(name)")
}
call(name: "Jason")
// 상수에 함수를 할당하고, 해당 상수를 호출
let callName = call
callName("Aha")
// 상수에 클로저 할당하고, 해당 상수 호출
let helloName = { (name: String) in
    print("hello, \(name)")
}
helloName("Oho")
// 클로저 형태: 함수와 거의 동일, in을 통해 파라미터 및 반환 타입과 실제 클로저 코드 분리
// { (name: String) -> Bool in ~ some code ~ }
// filter
let members = ["Jason", "Greg", "Tiffany"]
let nameHasT = members.filter { name in
    return name.hasPrefix("T")
}
// map
let prices = [1000, 2000, 3000]
let doubledPrices = prices.map { price in
    return price * 2
}
// reduce
let revenues = [100, 200, 300]
let totalRevenue = revenues.reduce(0) { partialResult, next in
    return partialResult + next
}

 

07. 클래스 구조체1

// ---------- 07. 클래스 구조체1 ----------
struct Album {
    // 멤버 변수들 - stored property
    let title: String
    let artist: String
    var isReleased = false
    
    // 생성자: 클래스 또는 구조체를 생성할 때 사용하는 특별한 함수(init 키워드로 선언)
//    init(title: String, artist: String) {
//        self.title = title
//        self.artist = artist
//    }
    func description() -> String {
        return "\(title) by \(artist)"
    }
    // 구조체 내부 멤버 변수의 값을 변경하는 경우, mutating 키워드 이용
    mutating func release() {
        self.isReleased = true
    }
}
var easyOnMe = Album(title: "Easy On Me", artist: "Adele")
print(easyOnMe.description())
print(easyOnMe.isReleased)
easyOnMe.release()
print(easyOnMe.isReleased)

// 클래스: 상속 가능   참조(reference)   생성자를 기본으로 만들어주지 않음
// 구조체: 상속 불가능  복사(copy)
class Employee {
    var name: String
    var hours: Int
    
    init(name: String, hours: Int) {
        self.name = name
        self.hours = hours
    }
    func work() {
        print("I'm working now...")
    }
    func summary() {
        print("I work \(self.hours) hours a day. ")
    }
}
class iOSDeveloper: Employee {
    override func work() {
        print("I'm developing iOS app now.")
    }
    override func summary() {
        print("I work \(self.hours/2) hours a day.")
    }
}
struct Phone {
    var modelName: String
    var manufacturer: String
    var version: Double = 1.0
}

let normalWorker = Employee(name: "Kim", hours: 8)
normalWorker.work()
normalWorker.summary()
//    I'm working now...
//    I work 8 hours a day.
let developer = iOSDeveloper(name: "Jason", hours: 8)
developer.work()
developer.summary()
//    I'm developing iOS app now.
//    I work 4 hours a day.

// Reference vs. Copy
var iPhone1 = Phone(modelName: "iPhone 13", manufacturer: "Apple")
var iPhone2 = iPhone1
iPhone2.modelName = "iPhone 14"
print(iPhone2.modelName)
print(iPhone1.modelName)
//    iPhone 14
//    iPhone 13
var jrDeveloper1 = iOSDeveloper(name: "John", hours: 8)
var jrDeveloper2 = jrDeveloper1
jrDeveloper1.name = "Billy"
print(jrDeveloper1.name)
print(jrDeveloper2.name)
//    Billy
//    Billy

 

08. 클래스 구조체2

// ---------- 08. 클래스 구조체2 ----------
// stored property: 클래스, 구조체가 값을 저장하고 있는 프로퍼티
// computed property: 따로 값 저장하지 않음, stored property를 활용학거나 특정값을 전달할 때 사용하는 프로퍼티
struct Watch {
    let model: String
    let manufacturer: String
    
    var description: String {
        return "\(model) by \(manufacturer)"
    }
}
struct Person {
    let firstName: String
    let lastName: String
    
    var fullName: String {
        return "\(firstName) \(lastName)"
    }
}
let appleWatch = Watch(model: "Watch 7", manufacturer:  "Apple")
print(appleWatch.description)
// Watch 7 by Apple
let jason = Person(firstName: "Jason", lastName: "Lee")
print(jason.fullName)
// Jason Lee

 

09. 프로토콜

// ---------- 09. 프로토콜 ----------
// 제공하고 싶은 역할(기능, 속성)을 미리 정의해 놓은 것
// 이후에 다른 타입이 해당 프로토콜의 역할을 제공하고 싶으면, conform해서 제공함
protocol Coach {
    var name: String { get set }
    var currentTeam: String { get }
    func training()
    func direct()
}
struct Mourinho: Coach {
    var name: String = "Jose Mourinho"
    var currentTeam: String = "AS Roma"
    
    func training() {
        print("Traing Player")
    }
    
    func direct() {
        print("Direct Game")
    }
}
let mourinho = Mourinho()
print("\(mourinho.name), \( mourinho.currentTeam)")
mourinho.training()
mourinho.direct()
//    Jose Mourinho, AS Roma
//    Traing Player
//    Direct Game

 

10. 익스텐션

// ---------- 10. 익스텐션 ----------
// 기존 타입에 새로운 역할(기능 및 속성)을 추가하고 싶을 때 사용
extension String {
    func contains(s: String) -> Bool {
        return self.range(of: s) != nil
    }
    func replace(target: String, with: String) -> String {
        return self.replacingOccurrences(of: target, with: with)
    }
}
let testString = "Hello iOS Developer!!"
let replaced = testString.replace(target: "Hello", with: "안녕하세요")
print(replaced)
//    안녕하세요 iOS Developer!!
print(testString.contains(s: "iOS"))
// true

 

11. 앞으로 스위프트 공부방향

swift language apple

https://developer.apple.com/kr/swift/

swift language guide

https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html

 


[Next] (2주차) 실습

2022.06.26 - [Swift] - [Xcode] iOS Swift 앱 개발 Byte Degree - week.02

 

[Xcode] iOS Swift 앱 개발 Byte Degree - week.02

iOS 아키텍처 패턴 중 애플에서는 기본적으로 MVC 패턴을 가이드함 Product Name: HelloiOS Organization Identifier: com.sehee Interface: Storyboard (other options: SwiftUI) Language: Swift (other option..

sarahee.tistory.com

 

 

728x90
728x90

+ Recent posts