책 검색 앱 만들기 (1)

오늘은 새로운 과제를 바로 시작해보자.

모두 코드를 사용해 구현해보기로 해서 이번에는 SnapKit을 적극적으로 한번 사용해 볼 예정이다. SPM을 통해 SnapKit을 넣어주고 이제 코드로 컴포넌트를 생성하고 레이아웃을 잡아보자!


TapbarController 생성

우선 두개의 탭을 가진 앱을 만들 예정이니 먼저 탭바를 넣어주자 탭바는 SceneDelegate에 구성할 예정이다. 탭바에 넣어 둘 VC를 미리 2개 만들어 두고 이름을 설정한다.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    
    guard let windowScene = (scene as? UIWindowScene) else { return }
    self.window = UIWindow(windowScene: windowScene)
    
    let tabBarVC = UITabBarController()
    
    let firstVC = SearchViewController()
    firstVC.tabBarItem = UITabBarItem(title: "책 검색", image: UIImage(systemName: "book"), tag: 0)
    
    let secondVC = MyPageViewController()
    secondVC.tabBarItem = UITabBarItem(title: "마이페이지", image: UIImage(systemName: "person.circle"), tag: 1)
    
    tabBarVC.viewControllers = [firstVC, secondVC]
    tabBarVC.tabBar.backgroundColor = .systemGray5
    
    self.window?.rootViewController = tabBarVC
    window?.makeKeyAndVisible()
}

그 다음 탭바 컨트롤러를 생성해 tabBarVC에 넣어준 후 첫 번째 두 번째 VC를 이전에 만들어둔 VC와 연결할 수 있도록 인스턴스에 담아둔다.

 

그 이후 탭바 아이콘과 탭명을 설정하고

tabBarVC.viewControllers = [firstVC, secondVC]

이렇게 탭바 뷰컨트롤러즈에 VC를 배열로 넣어주면 탭바는 완성된다.

 

앱을 빌드했을때 바로 탭바 먼저 호출될 수 있도록 rootViewController를 연결한다.

 

Compositional Layout 을 활용한 컬렉션 뷰 구현

이제 첫번째 VC에는 책 목록과 최근 본 책을 확인할 수 있도록 각각 컬렉션 뷰를 구성할 예정이다.

먼저 클로저를 활용해 컴포넌트들을 생성해주었다.

let searchController: UISearchController = {
   let searchController = UISearchController(searchResultsController: nil)
    searchController.obscuresBackgroundDuringPresentation = false
    searchController.searchBar.placeholder = "원하는 책을 검색해주세요."
    return searchController
}()

var scrollView: UIScrollView = {
    let scrollView = UIScrollView()
    scrollView.showsVerticalScrollIndicator = false
    return scrollView
}()

var recentlyLabel: UILabel = {
   let label = UILabel()
    label.text = "최근 본 책"
    label.textAlignment = .left
    return label
}()

var bookLabel:UILabel = {
    let label = UILabel()
    label.text = "BOOK"
    label.textAlignment = .left
    return label
}()

사용할 컴포넌트들을 이렇게 선언한 후 이제 컬렉션 뷰를 만들어줄 예정인데, 이번에는 저번에 사용해봤던 Compositional Layout을 사용해볼 생각이다.

var recentlyCollectionView: UICollectionView = {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .absolute(150))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(150))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = 5
        
        return section
    }
    
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionView.register(RecentlyViewedCollectionViewCell.self, forCellWithReuseIdentifier: "RecentlyCollectionViewCell")
    collectionView.backgroundColor = .blue
    return collectionView
}()

var bookCollectinView: UICollectionView = {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = 10
        return section
    }
    
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionView.register(BookCollectionViewCell.self, forCellWithReuseIdentifier: "BookCollectionViewCell")
    collectionView.backgroundColor = .gray
    return collectionView
}()

첫번째 컬렉션 뷰는 가로로 한줄로 구성된 컬렉션 뷰를 만들 예정이고,

두번째 컬렉션 뷰는 세로로 스와이프가 가능한 컬렉션 뷰를 생성해 볼 예정이다.

var recentlyCollectionView: UICollectionView = {
    let layout = UICollectionViewCompositionalLayout { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
        
        // 각 아이템의 크기 설정
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .absolute(150))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
        
        // 그룹 크기 설정
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(150))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        
        // 섹션 설정
        let section = NSCollectionLayoutSection(group: group)
        section.interGroupSpacing = 5
        
        return section
    }
    
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionView.register(RecentlyViewedCollectionViewCell.self, forCellWithReuseIdentifier: "RecentlyCollectionViewCell")
    collectionView.backgroundColor = .blue
    return collectionView
}()

각각 아이템, 그룹, 섹션을 설정한 뒤 값을 알맞게 넣으면 원하는 만큼 섹션이 나눠져 구성할 수 있다.

CompositionalLayout은 다음 페이지에 조금 더 자세하게 다뤄 볼 예정이다.

 

SnapKit을 사용해 오토레이아웃 잡기

이번 과제는 코드로 모든 내용을 작성해 볼 예정이기에 오토레이아웃을 조금 더 직관적이고 편리하게 구성할 수 있는 SnapKit을 사용했다.

func setupLayout() {
    scrollView.snp.makeConstraints {
        $0.edges.equalToSuperview()
    }
    
    contentView.snp.makeConstraints {
        $0.edges.width.equalToSuperview()
    }
    
    searchController.searchBar.snp.makeConstraints {
        $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(10)
        $0.leading.trailing.equalToSuperview().inset(10)
    }
    
    recentlyLabel.snp.makeConstraints {
        $0.top.equalTo(searchController.searchBar.snp.bottom).offset(10)
        $0.leading.trailing.equalToSuperview().offset(10)
    }
    
    recentlyCollectionView.snp.makeConstraints {
        $0.top.equalTo(recentlyLabel.snp.bottom).offset(10)
        $0.leading.trailing.equalToSuperview().inset(5)
        $0.height.equalTo(150)
    }
    
    bookLabel.snp.makeConstraints {
        $0.top.equalTo(recentlyCollectionView.snp.bottom).offset(10)
        $0.leading.trailing.equalToSuperview().inset(5)
    }
    
    bookCollectinView.snp.makeConstraints {
        $0.top.equalTo(bookLabel.snp.bottom).offset(10)
        $0.leading.trailing.equalToSuperview().inset(5)
        $0.height.equalTo(700)
    }
}

스냅킷을 사용하지 않고 오토레이아웃을 잡았다면 지금보다 훨씬 더 긴 코드들과 앵커들이 난무하는 코드가 됐었겠지만 스냅킷을 사용하니 이렇게 직관적이고 깔끔한 코드베이스 오토레이아웃이 가능하다 🙂

 

top.leading.trailing.bottom 이런식으로 같은 값을 넣을때 한줄에 모든 값을 다 넣을 수 있다는 점과 offset, inset을 활용해 더 직관적이고 편리하게 오토레이아웃을 구성할 수 있다는 점이 너무 좋았다.

 

이번 과제에 적극적으로 사용해 스냅킷을 완벽 숙달할 수 있도록 하자!

 

이렇게 각각 컴포넌트들과 오토레이아웃을 설정했으면 이렇게 구현이 완료된다 🙂

 

내일은 컴포지셔널 레이아웃을 다시 손보고 각 컬렉션 뷰에 셀을 올린 뒤 데이터 모델링을 해서 카카오 API 책 검색 데이터를 가져와 띄워 볼 예정이다.