[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 을 선택하면 앨범 탭으로 이동하여 지도가 아닌 올린 사진을 크게 볼 수 있도록 구현하였다.

세그먼트 가려지는 오류.gif

이 과정에서 탭을 변경하면 뜨는 이미지 뷰가 세그먼트 컨트롤러 보다 위에 존재하여 보이지 않아 이 점을 수정하기 위해 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 내 데이터를 넣으려고 하는 과정에서 이미지를 넣는 과정에 문제가 있었다.

 

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

스크린샷 2024-06-07 오후 1.07.56.png

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에서는 정상적으로 데이터가 들어가 사진과 같이 데이터가 들어가게 된다.

스크린샷 2024-06-07 오후 9.33.25.png

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

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에서 저장한 데이터가 잘 들어가는 것을 볼 수 있다.

데이터전달 및 파이어베이스 저장.gif