728x90
반응형

[Review] (9주차) 실습

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

 

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

[Review] (8주차) 실습 2022.08.26 - [Development/Swift] - [Swift] iOS 앱 개발(Xcode) Byte Degree - week.08 [Swift] iOS 앱 개발(Xcode) Byte Degree - week.08 [Review] (7주차) 실습 2022.08.25 - [Develop..

sarahee.tistory.com


2-5번째 내비게이션 탭 연동하기

첫번째로, 네번째 탭 연결

Main 탭의 Custom Class - Class, Identify - Storyboard ID 설정(CharViewController → FrameworkListViewController)

Inherit Module From Target 체크

 

기존 코드

//
//  MainTabBarController.swift
//  CarrotHomeTab
//
//  Created by sehee on 2022/08/24.
//

import UIKit

// 1. 탭이 눌릴 때마다, 그에 맞는 네비게이션 바를 구성하고자 함
// - 탭이 눌리는 것을 감지
// - 감지 후에, 그 탭에 맞게 네비게이션 바 구성 업데이트 필요


// 3. 앱이 시작할 때, 네비게이션바 아이템 설정을 완료하고 싶음
// - 네비게이션 바를

class MainTabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        updateNavigaitionItem(vc: self.selectedViewController!)
        //self.selectedIndex
        //self.selectedViewController
    }
    
    private func updateNavigaitionItem(vc: UIViewController) {
        switch vc {
        case is HomeViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "정자동",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let searchConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "magnifyingglass"),
                handler: { print("--> search tapped") })
            
            let searchItem = UIBarButtonItem.generate(with: searchConfig, width: 30)
            
            let feedConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "bell"),
                handler: { print("--> feed tapped") })
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)
            
            //let titleItem = UIBarButtonItem(title: "정자동", style: .plain, target: nil, action: nil)
            //let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem, searchItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        case is MyTownViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "정자동",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let feedConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "bell"),
                handler: { print("--> feed tapped") })
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        case is ChatViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "채팅",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let feedConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "bell"),
                handler: { print("--> feed tapped") })
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        case is MyProfileViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "나의 당근",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let settingConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "magnifyingglass"),
                handler: { print("--> setting tapped") })
            
            let settingItem = UIBarButtonItem.generate(with: settingConfig, width: 30)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [settingItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        default:
            let titleConfig = CustomBarItemConfiguration(
                title: "정자동",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            //let titleItem = UIBarButtonItem(title: "정자동", style: .plain, target: nil, action: nil)
            //let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = []
            navigationItem.backButtonDisplayMode = .minimal
        }
    }
}

// 2. 각 탭에 맞게 네비게이션바 아이템 구성하기
// - 홈: 타이틀, 피드, 서치
// - 동네활동: 타이틀, 피드
// - 내 근처: 타이틀
// - 채팅: 타이틀, 피드
// - 나의 당근: 타이틀, 설정

extension MainTabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        print("--> 어떤 vc가 선택:\(viewController)")
        
        updateNavigaitionItem(vc: viewController)
    }
}

변경사항

1) default display mode 및 FrameworkListViewController

2) 하단 extension의 print문은 주석 처리

        case is FrameworkListViewController:
            navigationItem.largeTitleDisplayMode = .always
            navigationItem.title = "애플"
            navigationItem.leftBarButtonItem = nil
            navigationItem.rightBarButtonItems = nil
            navigationItem.backButtonDisplayMode = .minimal
            
        default:
            navigationItem.largeTitleDisplayMode = .always
            navigationItem.title = nil
            navigationItem.leftBarButtonItem = nil
            navigationItem.rightBarButtonItems = nil
            navigationItem.backButtonDisplayMode = .minimal
        }

Cannot find type 'FrameworkListViewController' in scope

참고: 9주차 > Part8. 실전 프로젝트_Ch02. 당근 홈탭 리스트 따라해보기(Carrot Home) (1) > 07. 프로젝트 당근 홈탭 리스트 따라하기 (홈 구성하기) 07 (07:40~08:45)

Editor > Refactor to Storyboard... 클릭하여 AppleFramework.storyboard 생성 등

해결되지 않음

 

폴더를 복사 붙여넣기할 때, Create groups, Copy items if needed 체크 

다음과 같이 채팅 부분의 아이콘이 사라지며, 다음과 같은 소스 오류 코드 생성

Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

→ 해당 View를 스토리보드 Connection Inpsector에서 ViewController에 연결해주지 않아서 발생

 

다섯번째 탭도 동일하게 설정

 

 

728x90
728x90
728x90
반응형

[Review] (8주차) 실습

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

 

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

[Review] (7주차) 실습 2022.08.25 - [Development/Swift] - [Swift] iOS 앱 개발(Xcode) Byte Degree - week.07 [Swift] iOS 앱 개발(Xcode) Byte Degree - week.07 [Review] (6주차) 실습 2022.07.22 - [Develop..

sarahee.tistory.com


지금까지 구현한 내용

1) 각 탭 구성

2) 탭 별 다른 내비게이션 아이템 설정

3) Home의 Search, Feed, List 등 모두 구성

4) 서버로부터 받은 내용으로 List 구현

앞으로 구현할 내용

Detail view 구성


DetailViewModel.swift 및 ItemInfoDetails.swift 생성

 

HomeViewController에서 viewModel 설정

vc.viewModel = DetailViewModel(network: NetworkService(configuration: .default), itemInfo: item)
collectionView.delegate = self

extension HomeViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let item = viewModel.items[indexPath.item]
        viewModel.itemTapped.send(item)
    }
}

디자인 라벨 등 구성(완료)

코드 수정 방법

option key를 누르고, 커서 드래그 이후 좌우 방향키 이용하여 사용하지 않는 코드 삭제(혹은 생성 가능)

    // User
    @IBOutlet weak var userThumbnail: UIImageView!
    @IBOutlet weak var userNickName: UILabel!
    @IBOutlet weak var userLocation: UILabel!
    @IBOutlet weak var userTemperature: UILabel!
    
    // Item
    @IBOutlet weak var itemThumbnail: UIImageView!
    @IBOutlet weak var itemInfoTitle: UILabel!
    @IBOutlet weak var itemInfoDescription: UILabel!

KingFisher import하여 image 적용

상세뷰 우측 상단에 아이콘 삽입

DetailViewController의 viewDidLoad 함수에 configureNavigationBar() 추가

    override func viewDidLoad() {
        super.viewDidLoad()
        configureNavigationBar()
        bind()
        viewModel.fetch()
    }

Editor > Edit All in Scope (control + command + E)

가격 라벨 추가

@IBOutlet weak var itemPriceLabel: UILabel!

self.itemPriceLabel.text = "\(self.formatNumber(details.item.price))원"

extension DetailViewController {
    private func formatNumber(_ price: Int) -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        let result = formatter.string(from: NSNumber(integerLiteral: price)) ?? ""
        return result
    }

중간에 separate line 추가하여 완료

상세 오류 수정 #1

문제: 내용(Item Info Description)이 두 줄밖에 보이지 않는 현상

해결: Label Lines: 2 → 0 설정

상세 오류 수정 #2

문제: Detail View의 스크롤이 되지 않는 현상

Align Top to: Safe Area Equals: 44 삭제 및 Stack View 확인

Horizontal - Trailing/Leading Space to: Superview

Vertical - Bottom/Top Space to: Superview

설정하여 해결, 첫번째 내비게이션 바 완성

 

728x90
728x90
728x90
반응형

[Review] (7주차) 실습

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

 

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

[Review] (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/Swi..

sarahee.tistory.com


오류 시, Main.storyboard 삭제 후 다시 붙여넣기

Home UI 설정하기

Editor > Refactor to Storyboard... - Home

command + N > Swift - HomeViewModel

 

홈의 텍스트 및 버튼 삭제

Home 구성

Cocoa Touch Class 생성(Class: ItemInfoCell, Subclass of: UICollectionViewCell)

//
//  HomeViewController.swift
//  CarrotHomeTab
//
//  Created by sehee on 2022/08/24.
//

import UIKit
import Combine

// - 홈의 뷰 모델 만들기(리스트 가져오고, 아이템 탭 했을 때의 행동 정의)
// - 뷰 모델은 리스트 가져오기

class HomeViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    
    let viewModel: HomeViewModel = HomeViewModel(network: NetworkService(configuration: .default))
    var subscriptions = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureCollectionView()
        bind()
        viewModel.fetch()
    }
    
    private func configureCollectionView() {
        
    }
    
    private func bind() {
        viewModel.$items
            .receive(on: RunLoop.main)
            .sink { items in
                //self.applyItems(items)
                print("--> update collection view \(items)")
            }.store(in: &subscriptions)
        
        viewModel.itemTapped
            .sink { item in
                let sb = UIStoryboard(name: "Detail", bundle: nil)
                let vc = sb.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
                //vc.viewModel = DetailViewModel(network: NetworkService(configuration: .default), itemInfo: item)
                self.navigationController?.pushViewController(vc, animated: true)
            }.store(in: &subscriptions)
    }
}

Snapshot 설정하여 내부 페이지 확인

오류 수정 #1

Thread 1: "[<CarrotHomeTab.ItemInfoCell 0x7fd8d7846620> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key descriptionLable."

→ Xcode 내 코드에 연결시킨 button을 찾을 수 없음

뷰 내의 인스펙터가 연결 코드 정보와 상이하여 발생(연결 이후 라벨을 변경한 경우, Lable → Label)

ItemInfoCell 내 연결정보 변경

class ItemInfoCell: UICollectionViewCell {
    
    @IBOutlet weak var thumbnail: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    @IBOutlet weak var numOfChatLabel: UILabel!
    @IBOutlet weak var numOfLikeLabel: UILabel!

그래도 불가...

Referencing Outlets 해당 부분을 x

오류 수정 #2

Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

→ 1번과 동일한 문제

반복하여 연결 코드 삭제 후 재입력

해결

 

// - 좌우 패딩 필요

// - 셀에서, 콤마 표시하게끔 넘버 포매팅

// - 셀에서, 이미지 세팅하기 (+ cornerRadius 설정)

// - 셀에서, 콤마 표시하게끔 넘버 포매팅, formatNumber 변경
	func configure(item: ItemInfo) {
        titleLabel.text = item.title
        descriptionLabel.text = item.location
        priceLabel.text = "\(formatNumber(item.price))원"
        
        numOfChatLabel.text = "\(item.numOfChats)"
        numOfLikeLabel.text = "\(item.numOfLikes)"
    }

    private func formatNumber(_ price: Int) -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
//        formatter.numberStyle = .currencyPlural
//        formatter.locale = Locale(identifier: "ko-KR")
        let result = formatter.string(from: NSNumber(integerLiteral: price)) ?? ""
        return result
    }

 

Kingfisher download

Kingfisher: 원격 저장소,  쉽게 말해서 고유의 URL 주소를 가지고 있는 이미지를 앱 내에서 보여지게 해주는 라이브러리

project에서 CarrotHomeTab 선택, 상단바 Package Dependencies 선택, + 버튼 눌러서 Searching All Sources

혹은 File > Add Packages...

패키지 파일 존재하지 않음, 별도 다운로드 필요

 

File > Swift Packages > Add Package Dependency
Add https://github.com/onevcat/Kingfisher.git
Select "Up to Next Major" with "7.0.0"

 

https://github.com/wwood/kingfisher-download

 

GitHub - wwood/kingfisher-download: Easier download/extract of FASTA/Q read data and metadata from the ENA, NCBI, AWS or GCP.

Easier download/extract of FASTA/Q read data and metadata from the ENA, NCBI, AWS or GCP. - GitHub - wwood/kingfisher-download: Easier download/extract of FASTA/Q read data and metadata from the EN...

github.com

git clone https://github.com/wwood/kingfisher-download
cd kingfisher-download
conda env create -n kingfisher -f kingfisher.yml
conda activate kingfisher
cd bin
export PATH=$PWD:$PATH
kingfisher -h

Swift 내장 패키지 파일이 아니므로 Add Local... 불가

해당 URL 입력하여 download

https://github.com/wwood/kingfisher-download

다시 설치

https://github.com/onevcat/Kingfisher

 

GitHub - onevcat/Kingfisher: A lightweight, pure-Swift library for downloading and caching images from the web.

A lightweight, pure-Swift library for downloading and caching images from the web. - GitHub - onevcat/Kingfisher: A lightweight, pure-Swift library for downloading and caching images from the web.

github.com

Dependency Rule: Up to Next Major Version

import Kingfisher

        thumbnail.kf.setImage(
            with: URL(string: item.thumbnailURL),
            placeholder: UIImage(systemName: "hands.sparkles.fill")
        )

다음과 같이 생성(완료)


Storyboard에서 바로 깨어난 시점에서, 이미지의 모서리를 둥글게 설정

    override func awakeFromNib() {
        super.awakeFromNib()
        thumbnail.layer.cornerRadius = 10
        thumbnail.layer.masksToBounds = true
        thumbnail.tintColor = .systemGray
    }

잘못된 URL(이미지가 있는 서버 URL이 아닌)를 선택했을 때, place holder가 보여야 함

        thumbnail.kf.setImage(
//            with: URL(string: item.thumbnailURL),
            with: URL(string: ""),
            placeholder: UIImage(systemName: "hands.sparkles.fill")
        )

 

 

728x90
728x90
728x90
반응형

[Review] (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


당근 페이지(CarrotHomeTab)

Class: HomeViewController, DetailViewController 구성 (Subclass: UIViewController)

Main의 View Controller에서 상단바 Editor > Embed In > Navigation Controller / Tab Bar Controller 클릭

Class: MainNavigationViewController(UINavigationController), MainTabBarController(UITabBarController)

이후 Class 연결

DetailViewController의 Storyboard는 별도로 생성

    @IBAction func ctaButtonTapped(_ sender: Any) {
        let sb = UIStoryboard(name: "Detail", bundle: nil)
        let vc = sb.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
        navigationController?.pushViewController(vc, animated: true)
    }

실행했을 때 다음과 같은 에러 발생

Main의 entry point 설정

우측 Is Initial View Controller 체크 및 좌측 시작 화살표 확인

다시 실행하면 다음과 같이 UI 생성

Navigation Bar 조절 및 하단 이미지 등 UI 변경

홈: HomeViewController

동네 생활: MyTownViewController 생성 및 연결

내 근처 (설정하지 않음)

채팅: ChatViewController 생성 및 연결

나의 당근: MyProfileViewController 생성 및 연결

 

추가 구성

탭이 눌릴 때마다, 그에 맞는 네비게이션 바를 구성하고자 함
- 탭이 눌리는 것을 감지
- 감지 후에, 그 탭에 맞게 네비게이션 바 구성 업데이트 필요

import UIKit

class MainTabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "hello", style: .plain, target: nil, action: nil)
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
        
        delegate = self
    }
}

extension MainTabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        print("--> 어떤 vc가 선택:\(viewController)")
        
        switch viewController {
        case is HomeViewController:
            let titleItem = UIBarButtonItem(title: "정자동", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is MyTownViewController:
            let titleItem = UIBarButtonItem(title: "정자동", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is ChatViewController:
            let titleItem = UIBarButtonItem(title: "채팅", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        case is MyProfileViewController:
            let titleItem = UIBarButtonItem(title: "나의 당근", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
            
        default:
            let titleItem = UIBarButtonItem(title: "당근 당근", style: .plain, target: nil, action: nil)
            let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItem = feedItem
        }
    }
}

CustomBarItem 생성

MainTabBar 구성 변경하여 좌측 상단 text UI 설정


우측 상단 Item 개수 조절

MainTabBarController, delegate = self 하단에...

// 각 탭에 맞게 네비게이션바 아이템 구성하기
// - 홈: 타이틀, 피드, 서치
// - 동네활동: 타이틀, 피드
// - 내 근처: 타이틀
// - 채팅: 타이틀, 피드
// - 나의 당근: 타이틀, 설정

let searchConfig = CustomBarItemConfiguration(image: UIImage(systemName: "magnifyingglass"), handler: { print("--> search tapped") })
let searchView = CustomBarItem(config: searchConfig)
let searchItem = UIBarButtonItem(customView: searchView)

let feedConfig = CustomBarItemConfiguration(image: UIImage(systemName: "bell"), handler: { print("--> feed tapped") })
let feedView = CustomBarItem(config: feedConfig)
let feedItem = UIBarButtonItem(customView: feedView)

하단 let feedItem 삭제 및 수정

navigationItem.leftBarButtonItem = titleItem
navigationItem.rightBarButtonItems = [feedItem, searchItem]

 

실행하여 search 확인

            let searchConfig = CustomBarItemConfiguration(image: UIImage(systemName: "magnifyingglass"), handler: { print("--> search tapped") })
            let searchView = CustomBarItem(config: searchConfig)
            NSLayoutConstraint.activate([
                searchView.widthAnchor.constraint(equalToConstant: 30)
            ])
            
            let searchItem = UIBarButtonItem(customView: searchView)
            
            let feedConfig = CustomBarItemConfiguration(image: UIImage(systemName: "bell"), handler: { print("--> feed tapped") })
            let feedView = CustomBarItem(config: feedConfig)
            NSLayoutConstraint.activate([
                feedView.widthAnchor.constraint(equalToConstant: 30)
            ])
            
            let feedItem = UIBarButtonItem(customView: feedView)

UIBarButtonItem+CustomView 생성(command + N)

//
//  MainTabBarController.swift
//  CarrotHomeTab
//
//  Created by sehee on 2022/08/24.
//

import UIKit

// 1. 탭이 눌릴 때마다, 그에 맞는 네비게이션 바를 구성하고자 함
// - 탭이 눌리는 것을 감지
// - 감지 후에, 그 탭에 맞게 네비게이션 바 구성 업데이트 필요


// 3. 앱이 시작할 때, 네비게이션바 아이템 설정을 완료하고 싶음
// - 네비게이션 바를

class MainTabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        updateNavigaitionItem(vc: self.selectedViewController!)
        //self.selectedIndex
        //self.selectedViewController
    }
    
    private func updateNavigaitionItem(vc: UIViewController) {
        switch vc {
        case is HomeViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "정자동",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let searchConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "magnifyingglass"),
                handler: { print("--> search tapped") })
            
            let searchItem = UIBarButtonItem.generate(with: searchConfig, width: 30)
            
            let feedConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "bell"),
                handler: { print("--> feed tapped") })
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)
            
            //let titleItem = UIBarButtonItem(title: "정자동", style: .plain, target: nil, action: nil)
            //let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem, searchItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        case is MyTownViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "정자동",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let feedConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "bell"),
                handler: { print("--> feed tapped") })
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        case is ChatViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "채팅",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let feedConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "bell"),
                handler: { print("--> feed tapped") })
            let feedItem = UIBarButtonItem.generate(with: feedConfig, width: 30)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [feedItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        case is MyProfileViewController:
            let titleConfig = CustomBarItemConfiguration(
                title: "나의 당근",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            let settingConfig = CustomBarItemConfiguration(
                image: UIImage(systemName: "magnifyingglass"),
                handler: { print("--> setting tapped") })
            
            let settingItem = UIBarButtonItem.generate(with: settingConfig, width: 30)
            
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = [settingItem]
            navigationItem.backButtonDisplayMode = .minimal
            
        default:
            let titleConfig = CustomBarItemConfiguration(
                title: "정자동",
                handler: { })
            
            let titleItem = UIBarButtonItem.generate(with: titleConfig)
            
            //let titleItem = UIBarButtonItem(title: "정자동", style: .plain, target: nil, action: nil)
            //let feedItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: nil, action: nil)
            navigationItem.leftBarButtonItem = titleItem
            navigationItem.rightBarButtonItems = []
            navigationItem.backButtonDisplayMode = .minimal
        }
    }
}

// 2. 각 탭에 맞게 네비게이션바 아이템 구성하기
// - 홈: 타이틀, 피드, 서치
// - 동네활동: 타이틀, 피드
// - 내 근처: 타이틀
// - 채팅: 타이틀, 피드
// - 나의 당근: 타이틀, 설정

extension MainTabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        print("--> 어떤 vc가 선택:\(viewController)")
        
        updateNavigaitionItem(vc: viewController)
    }
}

내비게이션 바 UI 설정

//
//  MainNavigationViewController.swift
//  CarrotHomeTab
//
//  Created by sehee on 2022/08/24.
//

import UIKit

class MainNavigationViewController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let backImage = UIImage(systemName: "arrow.backward")
        navigationBar.backIndicatorImage = backImage
        navigationBar.backIndicatorTransitionMaskImage = backImage
        navigationBar.tintColor = .white
    }
}

 

 

728x90
728x90
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