UIkit Basic 5 (UITabBarController, UICollectionView, NotificationCenter)

Updated:

UITabBar

앱에서 서로 다른 하위작업, 뷰, 모드 사이의 선택을 할 수 있도록, tabBar 하나 혹은 하나 이상의 버튼을 보여주는 컨트롤 입니다 (flutter 의 BottomNavigationBar 기능임)

image

UITabController

다중 선택 인터페이스를 관리하는 컨테이너 뷰 컨트롤러로, 선택에 따라 어떤 자신 뷰 컨트롤러를 보여줄 것인지가 결정됩니다

탭바를 클릭할때마다 body 부분의 내용의 page 를 보여주는것을 컨트롤러를 통해서 변경합니다

image

UICollectionView

데이터 항목의 정렬된 컬렉션을 관리하고 커스텀한 레이아웃을 사용해 표시하는 객체 입니다

collectionView 는 리스트 형태로도 가능하고, 슬라이드와 같이 다양한 형태로 표현하는것이 가능합니다

image

예제코드

	// 저장된 diaryList 를 FlowLayout() 화면에 구현
	private func configureCollectionView() {
		self.collectionView.collectionViewLayout = UICollectionViewFlowLayout()
		self.collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
		self.collectionView.delegate = self
		self.collectionView.dataSource = self
	}

UICollectionViewLayout

레이아웃의 객체를 통해 CollectionView 아이템을 시작적 스타일을 나타냅니다. 뷰의 위치를 결정하고, 시작정 상태의 정보를

UICollectionViewFlowLayout

cell 을 원하는 형태로 정렬할 수 있는데, flowLayout cell의 선형 경로로 배치 할 수 있습니다. 최대한 행의 cell 을 계속 채워 넣다가 다 차개 되면 새로운 행을 만들어서 배치해나갑니다

image

예제코드

// collectionView 의 layout 구성
extension ViewController: UICollectionViewDelegateFlowLayout {
	// sizeForItemAt: size 를 설정하는 method 표시할 cell 의 사이즈를 CGSize 로 정의하고, return 해주면 설정한 size 대로 cell 에 표시됨
	func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
		// UIScreen.main.bounds.width 에 아이폰 사이즈 너비에 맞게 조절하고 / 2 해서 한 화면에 2개의 cell 이 나타나게 하며 -20 은 좌우 여백 10 의 합친 값이 20을 빼줘야 됨
		return CGSize(width: (UIScreen.main.bounds.width / 2) - 20, height: 200)
	}
}

flowLayout 을 사용하면 Grid 를 구현할 수 있지만, 더 다양한 모양으로도 구현할 수 있습니다

FlowLayout 의 구성요소

  • Flow 레이아웃 객체를 작성하고 컬렉션 뷰에 이를 할당합니다

  • 셀의 width, height 을 정합니다 (required) - 지정하지 않으면, cell의 너비와 높이가 0 이되어 내용이 나타나지 않게 됨

  • 필요한 경우 셀들 간의 좌우 최소 간격, 위아래 최소 간격을 설정하니다

  • section 에 header 와 footer 가 있다면 이것들의 크기를 지정합니다

  • 레이아웃의 스크롤 방향을 설정합니다

FlowLayout cell 사이의 간격

FlowLayout 은 cell 사이의 간격을 조절할 수 있습니다. 설정하는 간격은 최소간격을 배치하는것에 따라 지정한 값보다 설정한 값이 클 수 도 있음니다.

만약 cell 들의 크기가 같게 되면 최소로 설정한 간격을 지킬 수 있지만, cell 들의 크기가 제각각 다르게 되면 실제 간격이 다를 수 있음에 주의해야 합니다

image

행과의 간격 이외에도, section 자체의 공간을 줄 수 있습니다. (margin 같은 개념)

image

UICollectionViewDataSource

컬렌션 뷰로 보여지는 콘텐츠들을 관리하는 객체입니다. DataSource 를 정의하기 위해서는 UICollectionView 의 프로토콜을 준수해야 하며, dataSource 의 역활을 collectionView 에 몇개의 sectionView 가 있는지 특정 cell 에 몇개의 셀이 있는지 셀에 contents 를 보여 주기 위해 collectionView 에 재공하게 됩니다

public protocol UICollectionViewDataSource: NSObjectProtocol {
	// 지정된 섹션에 표시라 셀의 개수를 묻는 메서드 : numberOfItemsInSection
	func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int

	// 컬렉션뷰의 지정된 위치에 표시할 셀을 요청하는 메서드 : cellForItemAt
	func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell

	// 섹션의 개수를 묻는 메서드 : numberOfSections
	optional func numberOfSections(in collectionView: UICollectionView) -> Int
}

UICollectionViewDelegate

contents 의 표현, 사용자의 상호작용과 관련된 것들을 관리하는 객체 입니다

  • collectionView 와 관련된 핵심 객체들의 관계

image

// diary 화면에서 일기를 선택하였을때 일기 상세화면으로 이동하고, 일기 상세 내용을 볼 수 있습니다.
extension ViewController: UICollectionViewDelegate {
	// 특정 cell 이 선택 되었음을 알리는 cell 임
	func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
		guard let viewController = self.storyboard?.instantiateViewController(identifier: "DiaryDetailViewController") as? DiaryDetailViewController else { return }
		let diary = self.diaryList[indexPath.row]
		viewController.diary = diary
		viewController.indexPath = indexPath
		// Notification Center 로 대체
		// viewController.delegate = self
		self.navigationController?.pushViewController(viewController, animated: true)
	}
}

NotificationCenter

등록된 event 가 발생하면, 해당 event 들의 행동을 취하는것인데, 앱 내에서 어느곳에서나 메세지를 던지면, app 내 아무곳에서나 메세지를 받을 수 있는 역활입니다.

예제 코드

  • NotificationCenter post event 생성

	// .edit 일 경우에만 NotificationCenter post event 발생
		switch self.diaryEditorMode {
		case .new:
			let diary = Diary(
				uuidString: UUID().uuidString,
				title: title,
				contents: contents,
				date: date,
				isHeart: false)
			self.delegate?.didSelectReigster(diary: diary)

		case let .edit(indexPath, diary):
			let diary = Diary(
				uuidString: diary.uuidString,
				title: title,
				contents: contents,
				date: date,
				isHeart: diary.isHeart)
			NotificationCenter.default.post(
				name: NSNotification.Name("editDiary"),
				object: diary,
				userInfo: nil
			)
				// 더이상 userInfo 에 indexPath.row 값을 넘겨주지 않아도 됨 uuid 사용
				// userInfo: ["indexPath.row" : indexPath.row])
		}

		self.navigationController?.popViewController(animated: true)
	}
  • NotificationCenter observer 생성
	override func viewDidLoad() {
		super.viewDidLoad()
		self.configureCollectionView()
		self.loadDiaryList()
		// NotificationCenter editDiaryNotification observer
		NotificationCenter.default.addObserver(
			self,
			selector: #selector(editDiaryNotification(_:)),
			name: NSNotification.Name("editDiary"),
			object: nil
		)
		// NotificationCenter isHeart observer
		NotificationCenter.default.addObserver(
			self,
			selector: #selector(heartDiaryNotification(_:)),
			name: NSNotification.Name("heartDiary"),
			object: nil
		)
		// NotificationCenter deleteDiary observer
		NotificationCenter.default.addObserver(
			self,
			selector: #selector(deleteDiaryNotification(_:)),
			name: NSNotification.Name("deleteDiary"),
			object: nil
		)
	}

// select 생성을 해줘야 함

// Notification editDiary observer selector
	@objc func editDiaryNotification(_ notification: Notification) {
		guard let diary = notification.object as? Diary else { return }
		// 배열을 iteration 해서 전달 받은 uuid 와 같은 값이 배열의 요소에 있는지 확인하기, 있으면 해당 요소의 index를 return 받기
		guard let index = self.diaryList.firstIndex(where: { $0.uuidString == diary.uuidString }) else { return }
		self.diaryList[index] = diary
		self.diaryList = self.diaryList.sorted(by: {
			$0.date.compare($1.date) == .orderedDescending
		})
		self.collectionView.reloadData()
	}

	// Notification isHeart observer selector
	@objc func heartDiaryNotification(_ notification: Notification) {
		guard let heartDiary = notification.object as? [String: Any] else { return }
		guard let isHeart = heartDiary["isHeart"] as? Bool else { return }
		guard let uuidString = heartDiary["uuidString"] as? String else { return }
		guard let index = self.diaryList.firstIndex(where: { $0.uuidString == uuidString }) else { return }
		self.diaryList[index].isHeart = isHeart
	}

	// Notification deleteDiary observer selector
	@objc func deleteDiaryNotification(_ notification: Notification) {
		guard let uuidString = notification.object as? String else { return }
		guard let index = self.diaryList.firstIndex(where: { $0.uuidString == uuidString }) else { return }
		self.diaryList.remove(at: index)
		self.collectionView.deleteItems(at: [IndexPath(row: index, section: 0)])
	}

🔶 🔷 📌 🔑

reference

diary-ios-practice code - https://github.com/jacobkosmart/diary-ios-practice

김종권의 iOS 앱 개발 알아가기 - https://ios-development.tistory.com/103

fastcampus - https://fastcampus.co.kr/dev_online_iosappfinal

Categories:

Updated:

Leave a comment