개발을 잘하고 싶은 주니어?

Collection View 공부하기 (실습) 본문

개발/iOS

Collection View 공부하기 (실습)

데쿠! 2021. 11. 30. 03:48
반응형

flickr api를 이용해서 키워드를 입력하면 화면에 해당 키워드의 사진들이 보이게 할 예정입니다. 그리고 이번 실습에서는 MVVM 패턴으로 구현해보았습니다. (맞게 구현했는지는 잘 모르겠습니다..)

https://www.flickr.com/services/api/explore/flickr.photos.search 에서 키워드를 입력해서 호출하면 아래에 주소가 뜹니다. 거기서 api key를 얻어서 사용하면 됩니다. 

 

Flickr Api Explorer - flickr.photos.search

유용한 값 최근 공개 사진 ID: 51712997316 - snapshot 51712997526 - 2021 BlazinFast Football (364 of 752).jpg 51713867900 - Casamento Débora e Isaías - 21 11 21 인기 있는 공개 그룹 ID: 16978849@N00 - Black and White 34427469792@N01 - Flic

www.flickr.com

 

CollectionView 구성 - Storyboard

우선 가장 위에 검색에 필요한 textfield를 추가합니다. 그리고 나서 collectionview cell에 imageView를 꽉 차게 추가해줍니다.

또한 item size를 UICollectionViewDelegateFlowLayout을 이용해서 설정할 예정이기 때문에 Estimate Size를 None으로 설정합니다.

 

 

그리고 CollectionView의 dataSource와 delegate를 현재 뷰 컨트롤러로 설정해줍니다.

 

CollectionView 구성 - 코드

extension PhotosViewController: UICollectionViewDataSource{
	// 1
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel.photos.count
    }
    // 2
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as? PhotoCell else { return UICollectionViewCell() }
        cell.imageView.image = viewModel.photos[indexPath.item].thumbnail
        return cell
    }
}

1. 한 섹션 당 아이템 수를 리턴하는 메서드입니다. UICollectionViewDataSource 프로토콜을 채택했다면 필수로 구현해야 하는 메서드입니다.

2. cell의 정보를 알려주는 메서드입니다. 여기서는 cell에 필요한 설정을 구현하고 리턴해주면 됩니다.

collectionview에서 cell은 재사용되기 때문에 Reusable Queue로 cell을 관리합니다. 그래서 화면을 내릴 때 사라지는 cell은 이 queue로 들어가서 다시 재사용됩니다.

extension PhotosViewController: UICollectionViewDelegateFlowLayout{
	// 1
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = (view.frame.width / itemCnt)-20
        return CGSize(width: width, height: width)
    }
}

1. item size를 리턴하는 메서드입니다. 여기서는 한 row당 세 개의 item을 보여주기 위해서 뷰의 넓이를 3으로 나눈 후 20을 뺀 결과 값을 item의 넓이, 높이로 지정하였습니다.

 

데이터 가져오기 - Network

사진 데이터 - Photo

class Photo{
    var thumbnail: UIImage?
    let photoID: String
    let farm: Int
    let server: String
    let secret: String
    init(photoID: String, farm: Int, server: String, secret: String){
        self.photoID = photoID
        self.farm = farm
        self.server = server
        self.secret = secret
    }
    func getImageUrl(_ size: String = "m") -> URL?{
        return URL(string: "https://farm\(farm).staticflickr.com/\(server)/\(photoID)_\(secret)_\(size).jpg")
    }
}

사진에 대한 정보를 가지고 있는 클래스입니다. 이 클래스는 reference의 구조를 참고하였습니다.

View Model - PhotoViewModel

class PhotoViewModel{
    // 1
    static let shared = PhotoViewModel()
    var photos: [Photo] = []
    // 2
    func search(searchTerm: String, completion: @escaping ([Photo])-> Void){
        guard let url = URL(string: "https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=\(apiKey)&text=\(searchTerm)&per_page=20&format=json&nojsoncallback=1") else {
            print("invalid url")
            return }
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { data, response, error in
            guard error == nil else {
                print("error --> \(String(describing: error?.localizedDescription))")
                return
            }
            guard (response as? HTTPURLResponse) != nil, let data = data else { return }
            do{
                guard let resultDict = try JSONSerialization.jsonObject(with: data) as? [String:AnyObject] else { return }
                guard let photosContainer = resultDict["photos"] as? [String: AnyObject],
                      let photosReceived = photosContainer["photo"] as? [[String: AnyObject]] else { return }
                let photos = self.getPhotos(data: photosReceived)
                completion(photos)
            }catch{
                print("error ---> \(error.localizedDescription)")
            }
        }.resume()
    }
    // 3
    func getPhotos(data: [[String: AnyObject]]) -> [Photo]{
        let photos: [Photo] = data.compactMap { photoObj in
            guard let photoID = photoObj["id"] as? String, let farm = photoObj["farm"] as? Int ,let server = photoObj["server"] as? String ,let secret = photoObj["secret"] as? String else { return nil }
            let photo = Photo(photoID: photoID, farm: farm, server: server, secret: secret)
            guard let url = photo.getImageUrl(), let imageData = try? Data(contentsOf: url as URL) else {
                print("cannot get photos")
                return nil }
            guard let image = UIImage(data: imageData) else { return nil }
            photo.thumbnail = image
            return photo
        }
        return photos
    }
}

1. singleton 객체를 위해 static 상수를 추가하였습니다.

2. 사용자가 입력한 텍스트를 가지고 실제 정보를 가져오는 메서드입니다.

3. data를 실제 사진으로 변환하여서 리턴하는 메서드입니다. compactMap은 nil을 제거하고 옵셔널 바인딩해주는 고차 함수입니다.

고차 함수 : 다른 함수를 인자값으로 받거나 함수 실행의 결과를 함수로 반환하는 함수

 

결과

tree를 검색했을 때 나오는 결과 값

 

 

Reference

https://www.raywenderlich.com/18895088-uicollectionview-tutorial-getting-started

 

UICollectionView Tutorial: Getting Started

Get hands-on experience with UICollectionView by creating your own grid-based photo browsing app using the Flickr API.

www.raywenderlich.com

 

반응형
Comments