IAM iOS

[RxSwift] Implementing Photo Filter App Using RxSwift 본문

RxSwift

[RxSwift] Implementing Photo Filter App Using RxSwift

IAMiOS 2022. 3. 29. 21:53

 

What we will be Building?


Subject, Subscribe을 통한 이미지 데이터 전달

앨범의 사진을 가져올 PhotoCollectionViewController와
선택된 사진에 필터를 적용시킬 수 있는 페이지인 ViewController로 구성

 

PhotoCollectionViewController

 

ViewController

 

 

PhotoCollectionViewController


  • Subscribe가 가능한 UIImage를 반환할 Subject를 생성한다.
자체적으로 데이터를 생성할 수 있는 Observable의 역할

→ 보통 앱 개발에서 필요한 것은 실시간으로 Observable에 새로운 값을 수동으로 추가하고,
    subscriber에게 방출하는 것이 필요하다.

 


private let selectedPhotoSubject = PublishSubject<UIImage>()

var selectedPhoto: Observable<UIImage> {
     return selectedPhotoSubject.asObservable()
}

 

 

Subject객체에서 .asObservable()

 

Subject는 observer와 observable 둘의 역할을 다 하는데, 외부에서 observer에 접근하지 못하도록 설정하며 observable에만 접근할 수 있도록 나눠서 접근 가능하도록 하기 위함이다.

  • 사진(image)을 선택하면 selectedPhotoSubject.onNext 를 통해 선택된 새로운 항목(image)을 방출한다.
  • 그럼 ViewController에서 asObservable로 노출한 해당 Subject(selectedPhoto)를 display 하거나 subscribe 하여 이미지를 가져오면 된다.
override func collectionView(
	_ collectionView: UICollectionView,
        didSelectItemAt indexPath: IndexPath
    ) {
    
    let selectedAsset = self.images[indexPath.row]

	// 선택한 이미지 가져오기
    let manager = PHImageManager.default() // singleton
    
    manager.requestImage(for: selectedAsset, 
			targetSize: CGSize(width: 300, height: 300), 
			contentMode: .aspectFit, options: nil) { [weak self] image, info in
        
        guard let info = info else { return }
        
        let isDegradedImage = info["PHImageResultIsDegradedKey"] as! Bool
        if !isDegradedImage {
            if let image = image {
                self?.selectedPhotoSubject.onNext(image)
                self?.dismiss(animated: true, completion: nil)
            }
        }
    }
}

 

 

ViewController


  • 선택된 이미지에 접근하기 위해 Observable을 구독하여 이미지를 가져온다.
  • 이 과정에서 사진과 같이 앨범의 사진이 있는 PhotoCollectionViewController에서 선택한 사진이 ViewController의 image에 표시된다.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
		
	// 데이터를 전달할 viewController가 존재하는지 확인
    guard let navC = segue.destination as? UINavigationController,
          let photoCVC = navC.viewControllers.first as? PhotoCollectionViewController else { fatalError("Segue destination is not found") }
        
    // subscribe
    photoCVC.selectedPhoto
    	.subscribe(onNext: { [weak self] photo in
        	DispatchQueue.main.async {
          	  self?.updateUI(with: photo)
        	}
        }
    ).disposed(by: disposeBag) // 구독 폐기
}

 

private func updateUI(with image: UIImage) {
    self.photoImageView.image = image
    self.applyFilterButton.isHidden = false
}

 

 


참고) Subscribe가 하는 일

Subscribe는 Observable에 Observer를 연결해준다.

Observable을 Subscribe 하면 Subscribe 내부에서 Observer를 생성하고,

생성한 그 Observer를 내부에서 생성한 Observable에 붙이고

붙인 그 구독체를 반환한다.


 

 

이미지 Filter


FilterService

  • 필터 기능을 하는 FilterService를 생성
  • UI 이미지에 필터를 적용하고 처리 → 프로세스 이미지를 반환
class FilterService {

	...

	// 필터링된 파일에 대한 액세스를 제공
    func applyFilter(to inputImage: UIImage, completion: @escaping ((UIImage) -> ())) 
        
        // create Filter
        let filter = CIFilter(name: "CICMYKHalftone")!
        filter.setValue(2.0, forKey: kCIInputWidthKey) // 필터에 값 설정
        
        if let sourceImage = CIImage(image: inputImage) {
            filter.setValue(sourceImage, forKey: kCIInputImageKey)
            
            if let cgImg = self.context.createCGImage(filter.outputImage!, 
            					      from: filter.outputImage!.extent) {
                
                // 처리할 이미지
                let processedImage = UIImage(cgImage: cgImg, 
                			    scale: inputImage.scale, 
                                            orientation: inputImage.imageOrientation)
                completion(processedImage)
            }
        }
    }
}

 

 

이미지를 반환하는 대신 Observable로 Subscribe가 가능하도록 !!

  1. UIImage를 반환하는 Observable 생성 → observer 제공
  2. 2번 메서드를 호출하여 필터링된 이미지를 가져오고, 이제 observer를 호출
  3. Observable dispose시 특별한 처리가 필요 없기 때문에 Disposables.create()을 반환한다.
func (1)applyFilter(to inputImage: UIImage) -> Observable<UIImage> {
    return Observable<UIImage>.create { observer in
        self.(2)applyFilter(to: inputImage) { filterdImage in
            observer.onNext(filterdImage)
        }
        return Disposables.create()
    }
}

private func (2)applyFilter(to inputImage: UIImage, completion: @escaping ((UIImage) -> ())) {
	...
}

 

 

ViewController


이제 필터 버튼이 있는 ViewController로 와서 필터 기능을 적용한다.

 

  • applyFilter 버튼을 누르면 반환되는 Observable을 감지할 수 있으며, 실제로 Subscribe해서 실제 값을 얻을 수 있다.
  • onNext로 이미지를 되돌리면 필터링된 이미지를 제공한다.
@IBAction func applyFilterButtonPressed() {
    guard let sourceImage = self.photoImageView.image else { return }
    
    // subscribe
    FilterService().applyFilter(to: sourceImage)
        .subscribe(onNext: { filteredImage in
            DispatchQueue.main.async {
                self.photoImageView.image = filteredImage
            }
        }).disposed(by: disposeBag)
}

 

 

Before

 

After

 

 

코드 보러 가기

 

GitHub - camosss/RxSwift: RxSwift 공부한 내용 정리

RxSwift 공부한 내용 정리. Contribute to camosss/RxSwift development by creating an account on GitHub.

github.com