[Project 일지] 여행 기록 앱 만들기 (5) - sendSubviewToBack , 비동기 처리

2024.05.29 - [◽️ Programming/T I L] - [Project 일지] 여행 기록 앱 만들기 (1) - ScrollView , CollectionCustomView

2024.05.31 - [◽️ Programming/T I L] - [Project 일지] 여행 기록 앱 만들기 (2) - MapKit , CustomFlowLayout

2024.06.03 - [◽️ Programming/T I L] - [Project 일지] 여행 기록 앱 만들기 (3) - isExpanded , isUserInteractionEnabled

2024.06.05 - [◽️ Programming/T I L] - [Project 일지] 여행 기록 앱 만들기 (4) - scrollViewDidScroll, SegmentControl

 

 

오늘은 Firebase에 데이터를 저장하고 저장된 데이터를 불러와 입력창에 입력할 수 있게 진행하였으며, 세그먼트 컨트롤러의 계층을 변화시켜 안보이던 세그먼트를 보이도록 설정하는 작업을 진행하였다.

 

sendSubviewToBack

먼저 세그먼트 컨트롤러의 계층을 변화시켜 안보이던 세그먼트를 보이도록 설정하는 것을 알아보자

 

세그먼트 인덱스1 을 선택하면 앨범 탭으로 이동하여 지도가 아닌 올린 사진을 크게 볼 수 있도록 구현하였다.

이 과정에서 탭을 변경하면 뜨는 이미지 뷰가 세그먼트 컨트롤러 보다 위에 존재하여 보이지 않아 이 점을 수정하기 위해 sendSubviewToBack을 사용해 이미지 뷰를 뒤로 보내 화면을 구현하였다.

if sender.selectedSegmentIndex == 0 {
    mapView.isHidden = false
    albumImageView.isHidden = true
    mapAllButton.isHidden = false
    albumAllButton.isHidden = true
} else {
    mapView.isHidden = true
    albumImageView.isHidden = false
    mapAllButton.isHidden = true
    albumAllButton.isHidden = false
    if let firstImage = selectedImages.first {
        albumImageView.image = firstImage
    }
    contentView.sendSubviewToBack(albumImageView)
}

각 세그먼트 마다 보여져야 하는 콘텐츠를 isHidden을 활용해 보이고 안보이고를 설정하게 하였다.

 contentView.sendSubviewToBack(albumImageView)

이중에서 이부분을 바탕으로 이미지를 뒤로 보내 세그먼트가 보이도록 설정하였는데 이 과정에서 contentView 부분을 그냥 view.sendSubviewToBack 을 사용하니 제대로 작동되지 않아 애먹었다.

 

왜그러냐면 현재 세그먼트 컨트롤러는 view위에 올라와 있는것이 아니라 view 위에 올라와 있는 contentView위에 세그먼트 컨트롤러가 올라와 있기 때문에 view가 아니라 contentView를 넣어야 정상적으로 작동된다.

 

firebase 내 저장된 데이터 비동기 처리하여 불러오기

selectedImages = pinLog.media.compactMap { media in
    if let url = URL(string: media.url), let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
        return image
    }
    return nil
}

이번엔 firebase 내 저장된 데이터를 불러와 mytrip VC 내 데이터를 넣으려고 하는 과정에서 이미지를 넣는 과정에 문제가 있었다.

 

바로 사진에서 보는 것과 같이 해당 내용은 비동기로 처리해야하기 때문에 이런 에러가 발생하는 것이었다.

firebase에서 데이터를 불러와서 처리를 하기 때문에 이것은 비동기로 처리되어야 올바르게 앱이 작동될 수 있다.

 

그렇지 않으면 하나의 쓰레드에서만 작동하기엔 속도가 느려 이용자가 불편할 수 있으며, 앱이 정상적으로 작동하지 않을 수 있기 때문이다.

var tripLogs: [PinLog] = []
let pinLogManager = PinLogManager()

먼저 firebase에서 데이터를 가져올 수 있도록 백엔드에서 설정해 두었던 데이터 모델을 가져온다.

func loadData() async {
    do {
        guard let userId = Auth.auth().currentUser?.uid else { return }
        tripLogs = try await pinLogManager.fetchPinLogs(forUserId: userId)
        collectionView.reloadData()
    } catch {
        print("Failed to fetch pin logs: \\(error.localizedDescription)")
    }
}

그런 다음 현재의 유저 아이디를 토대로 동일한 아이디의 데이터를 가져와 상기 데이터 모델에 넣을 수 있도록 메서드를 구성한다.

func configure(with tripLog: PinLog) {
    if let imageUrl = tripLog.media.first?.url, let url = URL(string: imageUrl) {
        bgImage.kf.setImage(with: url)
    } else {
        bgImage.image = UIImage(systemName: "photo")
    }
    
    titleLabel.text = tripLog.location
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd"
    let startDate = dateFormatter.string(from: tripLog.startDate)
    let endDate = dateFormatter.string(from: tripLog.endDate)
    let duration = Calendar.current.dateComponents([.day], from: tripLog.startDate, to: tripLog.endDate).day ?? 0
    subTitle.text = "\\(startDate) - \\(endDate) (\\(duration) days)"
    
    privateButton.isHidden = tripLog.isPublic
}

그리고 각각 inputVC에서 작성한 데이터를 저장한 firebase 데이터를 마이트립 VC에 올바르게 넣어준다.

 

이렇게 들어가면 마이트립 VC에서는 정상적으로 데이터가 들어가 사진과 같이 데이터가 들어가게 된다.

다음은 해당 셀을 눌러 상세페이지로 이동해 작성했던 모든 데이터를 보여줘야 하는데 이 중에서 사진 목록을 불러오는 부분을 보자

func loadImage(from urlString: String, completion: @escaping (UIImage?) -> Void) {
    guard let url = URL(string: urlString) else {
        completion(nil)
        return
    }
    
    AF.request(url).response { response in
        if let data = response.data, let image = UIImage(data: data) {
            completion(image)
        } else {
            completion(nil)
        }
    }
}
func updateSelectedImages(with mediaItems: [Media]) {
    selectedImages = []
    let group = DispatchGroup()
    
    for media in mediaItems {
        guard let url = URL(string: media.url) else { continue }
        group.enter()
        loadImage(from: media.url) { [weak self] image in
            if let image = image {
                self?.selectedImages.append(image)
            }
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        self.galleryCollectionView.reloadData()
        if let firstImage = self.selectedImages.first {
            self.backgroundImageView.image = firstImage
        } else {
            self.backgroundImageView.image = UIImage(systemName: "photo")
        }
    }
}

DispatchGroup 를 사용해 비동기 처리를 진행하고 가져온 url 형식의 이미지를 변환해 상세 페이지 안에 위치한 컬렉션 뷰 셀에 들어갈 선택된 사진에 데이터를 넣어준다.

 

이렇게 변환된 이미지와 다른 내용들을 적절하게 넣어준 뒤

func configureView(with pinLog: PinLog) {
    locationLabel.text = pinLog.location
    
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy.MM.dd"
    
    dateStartLabel.text = dateFormatter.string(from: pinLog.startDate)
    dateEndLabel.text = dateFormatter.string(from: pinLog.endDate)
    
    let duration = Calendar.current.dateComponents([.day], from: pinLog.startDate, to: pinLog.endDate).day ?? 0
    dateDaysLabel.text = "\\(duration) Days"
    mainTitleLabel.text = pinLog.title
    subTextLabel.text = pinLog.content
    
    updateSelectedImages(with: pinLog.media)
    
    if let firstMedia = pinLog.media.first, let latitude = firstMedia.latitude, let longitude = firstMedia.longitude {
        let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
        let annotation = MKPointAnnotation()
        annotation.coordinate = coordinate
        mapView.addAnnotation(annotation)
        mapView.setRegion(MKCoordinateRegion(center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)), animated: true)
    }
}

넣어주면 정상적으로 상세페이지에도 inputVC에서 저장한 데이터가 잘 들어가는 것을 볼 수 있다.