📌 문제

파이어베이스에서 페이스북 로그인을 구현할 때 공식문서를 참고하여 Info.plist에 값을 넣었음에도 자꾸 다음과 같은 오류가 발생하였다.

 

오류 메시지를 읽어보면 info.plist 파일의 URL scheme 부분에 값이 제대로 들어가지 않았다고 한다.

오류가 난 info.plist 파일은 다음과 같다.

info.plist

 

 📌 문제 해결

나의 문제점은 URLSchemes의 아래 값을 넣어주는 부분에 fb를 붙여주어서 그렇다.

이런식으로 앞에 fb를 붙이지 않고 숫자만 넣어준다면 해결될 것이다.

 📌 결과 화면

아래와 같이 테이블뷰 안에 컬렉션뷰를 중첩한 구조를 만들려고 한다.

 

 📌 화면 구상

먼저 전체적으로 테이블뷰를 넣고 테이블뷰셀 안에 컬렉션뷰를 넣어주는 식으로 작업했다.

 

왼쪽부터 TV를 넣어준 SB, TVC(안에 CV) xib, CVC xib이다.

 

 📌 코드 - TV 설정

TV가 있는 스토리보드와 연결한 뷰컨트롤러 파일을 열어준다.

이후 스토리보드의 TV를 IBOutlet으로 연결해준다.

@IBOutlet weak var postTV: UITableView!

 

그리고 TVC를 등록하고 TVC의 delegate와 dataSource를 self로 설정해준다.

나는 해당 코드를 함수로 묶어 viewDidLoad에 넣어주었다.

private func setPostTV() {
    postTV.register(UINib(nibName: "PostTVC", bundle: nil), forCellWithReuseIdentifier: "PostTVC")
    postTV.delegate = self
    postTV.dataSource = self
}

 

UITableViewDelegate와 UITableViewDataSource 각각 코드를 작성해준다.

// MARK: - UITableViewDelegate
extension CreateMainVC: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        /// 만약 서버통신 후 받아온 각각의 게시글의 이미지 개수가 2개이면 iPhone 13 mini 기준 높이를 268, 3개이면 207로 설정해준다.
        let height = (postList[indexPath.row].images.count == 2) ? 268 : 207
        
        return height
    }
}

// MARK: - UITableViewDataSource
extension CreateMainVC: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        /// 총 TVC의 개수는 서버 통신 후 받아온 게시글의 총 개수
        return postList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: PostTVC.className, for: indexPath) as? PostTVC else { return UITableViewCell() }
        
        /// 순서대로 데이터 넣어주기
        /// TVC에 작성해준 setData 함수를 활용한다. 이때 CV를 위한 이미지 리스트도 전달한다.
        cell.setData(postList[indexPath.row])
        
        return cell
    }
}

 

 📌 코드 - TVC 설정

TVC xib파일과 연결한 UITableViewCell 파일을 열어준다.

이후 xib의 CV를 IBOutlet으로 연결해준다.

@IBOutlet weak var imgCV: UICollectionView!

 

그리고 CVC를 등록하고 CVC의 delegate와 dataSource를 self로 설정해준다.

나는 해당 코드를 함수로 묶어 viewDidLoad에 넣어주었다.

private func setImgCV() {
    imgCV.register(UINib(nibName: "ImgCVC", bundle: nil), forCellWithReuseIdentifier: "ImgCVC")
    imgCV.delegate = self
    imgCV.dataSource = self
}

그리고 TVC를 등록하고 TVC의 delegate와 dataSource를 self로 설정해준다.

나는 해당 코드를 함수로 묶어 viewDidLoad에 넣어주었다.

 

마지막으로 UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 각각 코드를 작성해준다.

TV에서 setData함수를 통해 넘겨준 데이터 중 imgList를 활용한다.

// MARK: - UICollectionViewDelegate
extension PostTVC: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        /// CVC 개수는 이미지 개수와 같다.
        return imgList.count
    }
}

// MARK: - UICollectionViewDataSource
extension PostTVC: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImgCVC.className, for: indexPath) as? ImgCVC else {
            return UICollectionViewCell()
        }
        /// CVC에 작성해준 setData 함수를 활용한다.
        cell.setData(imgList[indexPath.row])
        
        return cell
    }
}

// MARK: - UICollectionViewDelegateFlowLayout
extension PostTVC: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        /// 게시글 이미지 개수에 따라 너비와 높이를 정해준다.
        let cellWidth = (imgList.count == 2) ? 163 : 104
        let cellHeight = (imgList.count == 2) ? 170 : 109
        
        return CGSize(width: cellWidth, height: cellHeight)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        /// 게시글의 이미지 개수에 따라 인셋을 정해준다.
        if imgList.count == 2 {
            return UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
        } else {
            return UIEdgeInsets(top: 12, left: 18, bottom: 12, right: 18)
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        /// 게시글의 이미지 개수에 따라 CVC간 간격을 정해준다.
        let spacingSize = (imgList.count == 2) ? 17 : 14
        
        return CGFloat(spacingSize)
    }
}

 

 📌 문제

에셋을 가져와서 적용하고 빌드를 했을 경우 원래 이미지와 색상이 다르게 나오는 경우가 있다.

 

asset

왼쪽 이미지와 같이 원래 검정색 아이콘을 가지고 탭바를 구성해주었는데,

실제 빌드 해보면 오른쪽과 같이 선택된 탭인 경우는 default tint색인 blue가 적용되어서 나온다.

(왼쪽 이미지 아이콘이 회색인 이유는 선택되지 않은 탭이기 때문이다.)

 

 📌 해결 방법

Assets.xcassets에서 해당 에셋을 선택하고 오른쪽의 인스펙터 창에서 Render As를 Original Image로 설정해준다.

오른쪽 집 아이콘은 Render As를 Original Image로 변경한 것이고, 왼쪽의 이미지 아이콘은 Render As가 Default로 설정되어 있다.

 

 

+ 참고

만약 Asset의 Render As를 Original Image로 설정할 경우 tabBar Item에 tintColor를 변경해주어도 아무런 반응이 없다!

 📌 필요한 기능

인스타그램 클론코딩을 하면서 탭바를 구현하였는데 메인탭에 진입시에는 흰색 탭바 배경과 검정색의 아이콘들이 필요했고,

릴스탭을 누를 때는 탭바 배경색은 black으로, item들 색은 white로 변경해야 했다.

구현 후 화면은 다음과 같다.

인스타그램 메인

 📌 초기 설정

* 스토리보드로 구현하였기 때문에 인스펙터 창에서 설정해주었습니다.

먼저 초기 설정을 위해 TabBarController가 있는 스토리보드에서 탭바를 선택하고 오른쪽 인스펙터 창에서 Background로 White Color로 설정하였다.

그리고 User Defined Runtime Attributes에서 unselectedItemTintColor와 tintColot를 Black으로 설정하였다.

* tintColor는 선택된 탭바아이템, unselectedItemTintColor는 나머지 탭바아이템 색상이다.

 

이렇게 하면 메인화면으로 진입 시 아래와 같이 나온다.

 

 📌 Delegate

릴스탭을 선택할 경우 색상들을 바꿔주기 위해 Delegate를 사용했다.

코드를 먼저 보고 설명하도록 하겠다.

import UIKit

class TabBarController: UITabBarController {
    
    // MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setDelegate()
    }
}

// MARK: - Custom Methods
extension TabBarController {
    private func setDelegate() {
        self.delegate = self
    }
}

// MARK: - UITabBarControllerDelegate
extension TabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        if selectedIndex == 2 {
            tabBar.backgroundColor = .black
            tabBar.tintColor = .white
            tabBar.unselectedItemTintColor = .white
        } else {
            tabBar.backgroundColor = .white
            tabBar.tintColor = .black
            tabBar.unselectedItemTintColor = .black
        }
    }
}

먼저 UITabBarControllerDelegate 프로토콜 채택 작업을 진행한다. 아래 부분에 해당한다.

extension TabBarController: UITabBarControllerDelegate {}

* 저는 가독성을 위해 extension으로 빼준 것이므로 아래와 같이 작성해주는 것도 가능합니다.

class TabBarController: UITabBarController, UITabBarControllerDelegate {}

이후 Delegate 대리자를 설정해주어야 한다.

self.delegate = self

이 코드를 viewDidLoad()에 작성해준다.

* 저는 함수로 빼주었습니다.

 

해당 코드는 탭바 아이템을 선택했을 때 TabBarController가 delegate를 사용하기 위한 것이다.

 

 📌 색상 변경

func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
    if selectedIndex == 2 {
        tabBar.backgroundColor = .black
        tabBar.tintColor = .white
        tabBar.unselectedItemTintColor = .white
    } else {
        tabBar.backgroundColor = .white
        tabBar.tintColor = .black
        tabBar.unselectedItemTintColor = .black
    }
}

tabBarController 함수는 tabBar delegate method로 탭이 선택되었을 때 작동한다.

 

selectedIndex는 몇 번째 뷰컨을 선택했는지에 대한 인덱스 값이다.

인덱스 값은 0부터 시작하므로 릴스탭은 인덱스 값이 2이다.

 

따라서 selectedIndex 값이 2일 경우 배경을 검정으로, 아이템을 흰색으로 바꿔주는 작업을 하면 된다.

추가로 selectedIndex 값이 2가 아닐 경우는 다시 배경을 흰색으로, 아이템을 검정색으로 바꿔주는 작업이 필요하다.

 

총 세 개의 속성을 주면 된다!

tabBar.backgroundColor = .black // 탭바의 배경 색상을 바꿔준다.
tabBar.tintColor = .white // 선택된 탭바 아이템의 색상을 바꿔준다.
tabBar.unselectedItemTintColor = .white // 선택되지 않은 탭바 아이템의 색상을 바꿔준다.

 📌 들어가며

비밀번호 입력창에 눈 아이콘 버튼을 두고 이 버튼을 선택함에 따라 비밀번호 보기/숨기기를 구현하려고 한다.

눈 아이콘 선택 시 비밀번호 보기/숨기기

 

해당 뷰는 스토리보드로 구현하였으며 해당 TextField와 Button 네이밍은 다음과 같다.

	@IBOutlet weak var passwordTextField: UITextField!
	@IBOutlet weak var passwordEyeButton: UIButton!

 

passwordTextField는 .isSecureTextEntry속성 default 값을 true로 주었다.

 

 📌 필요한 로직

이 기능을 구현하기 위해서는 세 가지 설정이 필요하다.

  • 버튼 선택 상태 변경
  • 비밀번호 보기/숨기기
  • 눈 아이콘(버튼 이미지) 변경

 

 📌 코드

    @IBAction func passwordEyeButtonDidTap(_ sender: Any) {
        // 보안 설정 반전
        passwordTextField.isSecureTextEntry.toggle()
        // 버튼 선택 상태 반전
        passwordEyeButton.isSelected.toggle()
        // 버튼 선택 상태에 따른 눈 모양 이미지 변경
        let eyeImage = passwordEyeButton.isSelected ? "password shown eye icon" : "password hidden eye icon"
        passwordEyeButton.setImage(UIImage(named: eyeImage), for: .normal)
        // 버튼 선택된 경우 자동으로 들어가는 틴트 컬러를 투명으로 변경해줌
        passwordEyeButton.tintColor = .clear
    }

 

 📌 마치며

처음에 eyeButton.isSelected 값을 바꿔주는 코드를 작성하지 않아서 비밀번호 보안 설정은 바뀌지만 눈 모양 아이콘 변경이 되지 않았다.

passwordTextField 보안 설정을 toggle()로 변경해준 코드를 보고 passwordEyebButton도 바꿔줬다고 착각해서 삽질을 했다는 거~  🫠

눈을 제대로 뜨자 👀

 

📌 프로젝트 생성

Xcode를 다운로드하고 처음 키면

이런 화면이 반겨줄텐데요,
새로운 프로젝트 생성을 위해 Create a new Xcode project를 눌러줍니다.
 
또는
 

왼쪽 상단 File - New - Project를 누르거나 단축키인 Shift + command + N을 눌러도 생성이 가능합니다.
 
 
프로젝트를 생성한다면 아래와 같이 많은 선택지가 나옵니다.

Application을 하나씩 알아볼게요!
 

1. App
    - 뷰를 사용하여 앱을 개발할 때 쓰는 템플릿 

2. Document App
    - 데이터를 저장할 수 있는 문서 기반의 앱을 개발할 때 사용하는 템플릿

3. Game
   - 게임 앱을 개발할 때 사용하는 템플릿

4. Argumented Reality App
    - 증강현실(AR) 앱을 개발할 때 사용하는 템플릿

5. Swift Playgrounds App
    - 플레이그라운드 앱을 개발할 때 사용하는 템플릿

6. Sticker Pack App
    - 스티커 팩 앱을 개발할 때 사용하는 템플릿

7. iMessageApp
    - iMessageApp을 개발할 때 사용하는 템플릿

8. Safari Extension App
    - 사파리 브라우저 확장 앱을 개발할 때 사용하는 템플릿

 
이 많은 템플릿들 중에 일반적으로 가장 많이 사용하는 건 App입니다 !!!!
그럼 App을 선택한 후 프로젝트 생성을 계속 진행해보겠습니다.

제가 처음 프로젝트를 생성할 때 당황했던 부분인데요.
여기도 하나 씩 설명해보도록 하겠습니다.
 

1. Product Name
    - 앱의 고유번호인 Bundle Identifier을 만드는 요소로 사용됩니다.
    - 사용할 프로젝트 이름을 넣으면 됩니다! ex) 1st-Seminar, DooriBon, NadoSunbae

2. Team
    - 애플 개발자로 생성된 인증서를 선택하는 부분입니다.
    - 애플 개발자 사이트에서 발급 받은 인증서가 있다면 목적에 맞게 서명된 앱을 만들어 낼 수 있습니다.
    - 만약 애플 개발자 계정이 없다면 Personal Team인 개인 계정을 넣어주면 됩니다.

3. Organization Identifier
    - 프로젝트 운영 기관 또는 조식의 식별값을 입력합니다.
    - 대체로 소속 회사나 해당 프로젝트의 도메인에서 www를 뺀 나머지 역순으로 사용합니다.
    - 이 값은 Product Name과 함께 Bundle Identifier의 구성 요소가 됩니다!

4. Bundle Identifier
    - 앱의 고유 식별 번호입니다.
    - Organization Identifier + Product Name으로 구성됩니다.

5. Interface
    - Interface를 구현할 때 사용되는 방법을 선택합니다.
    - 지금은 StoryBoard 방식을 선택하도록 하겠습니다.

6. Language
    - 앱을 개발할 때 사용할 언어를 선택합니다.
    - 지금은 Swift를 선택하겠습니다.

7. Use Code Data
    - 기기 내부에 데이터를 저장하는 경우, 이를 지원하는 코어 데이터를 사용할 것인지를 묻는 옵션입니다.
    - 사용 여부에 따라 선택하면 됩니다.

8. Include Tests
    - 개발한 소프트웨어의 품질을 테스트한 결과를 확인할 수 있는 단위 테스트 기능과 UI 요소에 따른 테스트를 할 수 있습니다.
    - 필요 여부에 따라 선택하면 됩니다.

 

Next를 누른 후, 원하는 경로를 선택하고 Create를 누르면 프로젝트 생성이 완료됩니다!

무사히 프로젝트가 생성되었네요 👏
 

 📌 프로젝트 기초 구성

- Navigation Area
   : 파일 탐색 및 파일 추가, 이슈 등 여러 정보를 하는 영역

왼쪽부터

  • Project navigator
    - 작업중인 프로젝트 파일 구성을 보여준다.
    - 새로운 파일을 추가하거나 삭제, 수정이 가능하다.
  • Source Control navigator
    - 소스 파일 버전 관리를 위한 네비게이터이다.
    - 레포지토리라고 불리는 소스 관리 전용 공간을 생성했을 때만 사용 가능하다. (프로젝트 생성 시 하단에 있는 Create Git repository on my Mac 선택 또는 Source Control -> Create Git Repositories 선택)
  • Symbol navigator
    - 프로젝트에서 작성된 클래스나 구조체, 메소드 등의 심벌을 구조적으로 관리할 수 있는 네비게이터이다.
  • Find navigator
    - 프로젝트 내부 내용을 검색할 때 사용한다.
  • Issue navigator
    - 코딩 중 문법 오류나 문제가 생길 경우 경고나 오류 표시가 나타난다.
  • Test navigator
    - 프로젝트 테스트 목적으로 생성된 정보를 보여준다.
  • Debug navigator
    - 디바이스 혹은 시뮬레이션이 실행되면 각종 정보를 보여준다.
    - 디버깅 중에만 정보가 표시된다.
  • Breakpoint navigator
    - 실행 중간에 코드의 진행을 멈추고 메모리나 변수값 등을 확인할 때 사용한다.
    - 코드 내 원하는 위치에 브레이크 포인터를 삽입하고 해당 목록을 확인할 수 있다.
  • Report navigator
    - 빌드 결과나 실행 결과 등을 확인할 수 있다.
    - 필터와 함께 사용할 수 있다.

 

- Editor Area
   : 코드 작성, 화면 설계 등의 작업을 하는 영역

  • 하단의 폰 모양 버튼을 누르면 어떤 기종으로 작업을 할지 정할 수 있습니다.

얼마 전 제인초이님께서 스토리보드 작업 관련 조언을 주셨는데,
사이즈가 큰 기종으로 작업을 하고 작은 기종에서 잘리는 것 << 사이즈가 작은 기종으로 작업을 하고 큰 기종에서 여백이 남는 것
이기 때문에 mini로 작업을 하는 것을 추천한다고 하셨습니다 🫶🏻
 

- Inspector Area
   : 선택한 파일에 대한 정보를 나타내는 영역

왼쪽부터

  • File inspector
    - 선택된 파일에 대한 정보를 보여준다.
    - story board라면? 파일 정보, 빌드 대상 iOS 버전, 자동 레이아웃 옵션 등 설정하는 항목 표시
    - 화면 크기에 대응하는 앱, 국가별 언어 설정 처리 가능
  • History inspector
  • Quick Help inspector
  • Identity inspector
    - 인터페이스 빌더에 추가된 객체와 이를 구현한 클래스 사이의 연결, 객체의 ID 등 고유 정보 관리를 할 수 있다.
    - UI 객체를 소스 코드에서 참조하기 위한 식별 값을 정의할 수 있다.
    - 소스 코드와 화면 상의 객체를 연결할 수 있다.
  • Attributes inspector
    - 선택된 객체의 속성을 관리한다.
  • Size inspector
    - 선택된 객체의 외형에 대한 속성을 관리한다.
    - 주로 크기, 위치, 배치 방식 등을 다룰 수 있다.
  • Connections inspector
    - 인터페이스 빌더와 소스 코드 간의 연결 관계를 관리할 수 있다.

 

- Debug Area
   : 디버그 관련 내용 표시되는 영역

  • 에러 로그, 출력 로그 등 확인 가능합니다.

 

- Tool Bar
   : 핵심 버튼이 있는 영역

  • ▶️ 버튼을 누르면 빌드가 되고, 시뮬레이터가 작동됩니다.
  • 이미지 중간 부분의 'iPhone 11'을 누르면 시뮬레이터 기종을 선택할 수도 있습니다.
    (여기서 아이폰을 유선 연결해서 시뮬레이터로 설정할 수 도 있습니다.)

 

각 부분마다 이런 아이콘이 있을 겁니다!
이걸 눌러주면 해당 부분을 접었다 폈다 할 수 있습니다.
저도 14인치를 쓰고 있기 때문에.. 접어 놓고 쓰는 게 눈이 조금이나마 편하더라구요.. 하하
 
 
다음으로는 파일들에 대해 살펴보겠습니다.
 

1. *.swift

    - Swift 클래스 파일로 앱의 소스 코드를 구성하는 역할을 합니다.
    - AppDelegate, SceneDelegate 파일은 앱 전체의 생명 주기를 관리하는 클래스 파일입니다.
        * 생명주기에 대해서는 따로 포스팅하도록 하겠습니다!
    - ViewController 파일은 화면(뷰)에서 처리하는 작업들을 처리하기 위한 소스 코드를 담고 있는 클래스 파일입니다.
      storyboard에 가면 화면이 보일텐데요, 일반적으로 화면 개수만큼 ViewController가 필요합니다!
 

2. *.storyboard

    - 유저 인터페이스를 종합적으로 구현하는 역할을 합니다.
    - Main.storyboard 파일은 프로젝트 생성 시 기본으로 생성이 되고, 앱의 GUI를 담당합니다.
    - LaunchScreen.storyboard 파일은 앱을 실행하면 처음 나타나는 시작 화면의 인터페이스 설계를 담당합니다. (스플래시)
    - 화면 간의 연결을 보여줍니다.
    - View Controller와 View를 담는 공간입니다.
    - 처음 iOS 개발을 공부하게 되면 이 스토리 보드에서 화면 구현을 공부할 거예요!
 

잠깐, View Controller는 뭐고, View는 뭔데 ?!

간단하게 View Controller 안에 View가 있다고 생각하면 될 것 같습니다.

View Controller는 뷰의 계층 구조를 관리하는 것입니다.
뷰 컨트롤러에는 모든 콘텐츠를 포함하는 하나의 루트 뷰(Root View)가 있으며,
이 루트 뷰에는 콘텐츠를 표시하는 데에 필요한 뷰(View)를 추가합니다.

View는 iOS 앱 사용자 인터페이스를 구성하는 기본 구성 요소입니다.
뷰를 사용해서 화면에 컨텐츠를 배치하거나 그릴 수 있으며 뷰 안에 또 다른 뷰가 들어갈 수 있습니다.
UI 컴포넌트인 Label이나 Button 등도 하나의 View 입니다.


‼️ ViewController.swift 파일과 View Controller는 다릅니다!!!!!

위에서 swift 파일에 대해 언급을 했 듯이 ViewController 파일은 작업에 필요한 소스 코드를 담고 있는 파일입니다.
먼저 ViewController.swift 클래스 파일은 말 그대로 '클래스' 파일이기 때문에 storyboard에 있는 View Controller와 연결해서 사용하게 됩니다.
그럼 해당 View Controller에서 ViewController.swift 파일에 있는 소스 코드를 사용할 수 있겠죠?!

 

3. Assets.xcassets

    - 이미지 및 아이콘 등의 리소스를 관리할 때 사용하는 폴더입니다.
    - 슬라이더를 커스텀하거나 앱의 아이콘 등등이 포함될 수 있겠죠?!
 

4. info.plist 

    - 프로젝트 설정을 담당하는 파일입니다.
    - 시작화면 설정, 네트워크 접근 허용, 카메라 접근 허용, 위치 접근 허용 등이 있습니다.
 

 📌 마치며

기깔난 세미나 자료를 만들어 주신 솝트 30기 iOS 파트장 태끼님께 감사의 말씀을 전합니다...
 
 
참고
* SOPT 30th iOS 1차 세미나 자료
* https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sqlpro&logNo=221045495843 
 

+ Recent posts