NotificationCenter

애플 공식 문서에는 이렇게 적혀있다

A notification dispatch mechanism that enables the broadcast of information to registered observers.

등록된 Observer들에게 정보를 BroadCast할 수 있는 알림 발송 메커니즘

 

이전에 사용했을 때는 주로 키보드가 올라올 때, 내려갈 때 TextField가 가려지는 문제를 해결하기 위해서 사용한 경험이 있다.

오늘은 좀 더 자세히 알아보자


출처 : https://baked-corn.tistory.com/42

어떤 객체가 Post를 하면 Observe하고 있던 Observer들에게 BroadCast를 한다.

(....? Delegate랑 똑같은 거 아냐?)

맞다. 어떤 객체가 다른 객체와 소통/송수신 한다는 메커니즘은 같다

 

Delegate 패턴의 사용 예

protocol SomeDelegate{
	func sayName(name: String)
}

class FirstView: UIView{
	// 순환 참조 사이클을 막기 위해 강한 참조가 아닌 약한 참조로 선언한다
    // 이벤트를 발생할(책임을 전가하는) 타입에서는 Delegate를 프로퍼티로 선언한다
	weak var delegate: SomeDelegate?
    
    func tappedButton(){
    	delegate?.sayName(name: "tree")
    }
}

// 동작을 수행할 타입에는 Delegate를 채택한다
class SomeController: SomeDelegate{
	var view: SomeView = SomeView()
    
    init(){
    	view?.delegate = self
    }
    
    // SomeDelegate 채택 후, 준수
    func sayName(name: String){
    	print(name) // tree
    }
    
} 

크게 5가지만 생각하면 된다

  1. Delegate 패턴을 위한 Protocol 선언
  2. 책임을 전가할 타입(위임자)에서 delegate를 프로퍼티로 선언 -> 이때, 순환 참조 사이클을 막기 위해 약한 참조 사용
  3. 동작을 수행할 타입(대리자)에서 delegate를 채택
  4. 위임자에 있는 delegate를 대리자의 객체로 초기화
  5. 대리자에서 프로토콜을 준수

장점 : 코드를 이해하며 따라가기 쉬움

단점 : 많은 객체들에게 이벤트를 알려주는 것이 어렵고 비효율적

 

 

Notification

- NotificationCenter 라는 싱글톤 객체를 통해 이벤트들의 발생 여부를 옵저버에게 알림.

- Notification.Name 이라는 Key 값을 통해 Post/Observe 가능

 

class PostVC: UIViewController{
	@IBOutlet var button: UIButton!
    
    @IBAction func sendNotification(_ sender: UIButton){
    	NotificationCenter.default.post(name: Notification.Name("buyEmoticon"), object: nil)
    }
}


class ViewController: UIViewController{
	override func viewDidLoad(){
    	super.viewDidLoad()
        
        NotificationCenter.default.addObserver(self, selector: #selector(sayHello(noti:)), name: Notification.Name("buyEmoticon"), object: nil)
        
    }
    
    deinit{
    	NotificationCenter.default.removeObserver(self)
    }
    
    @objc func sayHello(noti: Notification){
    	print("Hello") // Hello
    }
}

 

장점

  • 많은 줄의 코드가 필요없이 이벤트/동작을 구현할 수 있다
  • 다수의 객체들에게 동시에 이벤트의 발생을 알려줄 수 있음

단점

  • 추적이 쉽지 않음
  • post 이후 정보를 받을 수 없음

 

결론

  1. 가능하다면 Delegate로 구현하되, 하나의 이벤트로 다수의 객체에서 어떠한 동작을 해야하는 경우에만 NotificationCenter를 사용
  2. NotificationCenter를 사용해야 하는 이벤트들의 경우 NotificationCenter 사용

 

 

프로젝트가 내가 만든 프로젝트가 아니거나, 저장 공간을 옮겼을 때 나타나는 현상으로 보인다.

 

해결 방법!!

 

말 그대로 ..../경로에 Info.plist가 없어서 빌드가 되지 않는 것이다.

따라서 방법은 2가지

  1. 해당 경로에 Info.plist를 가지고 간다. -> 옮긴다
    1. 해보진 않았지만, 될 것 같다!!

1. Xcode 내의 프로젝트를 누른다

대충 이런 화면이 나온다

 

2. 좌측에 PROJECT와 TARGETS 중 TARGETS에 있는 자신의 프로젝트 이름을 클릭한다.

 

3. 뭐 Architectures, Assets, Build Locations ... 등 많은데 Packaging 을 찾는다!

 

4. 눈 뜨고 잘 보면 Info.plist라는 항목이 보인다.

 

5. 해당 항목에 있는 Value를 에러에서 나온 경로를 붙여준다.

 

 

6. 열심히 작업한다...

 

이 현상만 3번 겪었는데, 3번만에 작성하는 내가 웃기다...

이제는 그때그때마다 작성해놔야겠다

앞선 포스팅에 이어서 작성된다.

앞선 블로그(https://wlgusdn700.tistory.com/28) 에 가면 iOS에서 화면 캡처하는 것을 알 수 있고 그 캡처된 이미지를 UIImage로 불러올 수 있다.

 

나는 이 캡처된 UIImage를 인스타 스토리에 공유할 것이다!

당연히 카카오/구글/페북 developer 사이트에 방법은 나와있다!

developers.facebook.com/docs/instagram/sharing-to-stories 이곳에 가면 있다.

근데 왜 적냐? 코드가 Objective-C 다.... 안드로이드는 java -> kotlin 이 사실상 문법이 비슷비슷해서 이해할 수 있겠는데...

Ob-C를 내가 어떻게 아냐고....ㅜ

사실 Ob-C를 볼줄이라도 아는 사람들은 곧바로 Swift로 변환이 가능할 것이다. 나도 Objective-C 문법 블로그를 찾아 보면서 변환했다.

 

func backgroundImage(backgroundImage: UIImage) {
        if let urlScheme = URL(string: "instagram-stories://share") {
            if UIApplication.shared.canOpenURL(urlScheme) {
                let pasteboardItems = [["com.instagram.sharedSticker.stickerImage": backgroundImage.pngData(),
                                        "com.instagram.sharedSticker.backgroundImage": backgroundImage.pngData()]]

                let pasteboardOptions = [UIPasteboard.OptionsKey.expirationDate: Date().addingTimeInterval(60 * 5)]

                UIPasteboard.general.setItems(pasteboardItems, options: pasteboardOptions)

                UIApplication.shared.open(urlScheme as URL, options: [:], completionHandler: nil)
            } else {
                print("인스타 앱이 깔려있지 않습니다.")
            }
        }
    }

코드는 굉장히 간단하다

url로 인스타 앱 중 스토리로 공유하는 화면?을 열것이다.

우선 해당 url을 iOS App에서 열수 있는지 확인을 한다. 인스타 앱이 안깔려있어도 못 여는 것 같다.

pasteboardItems 에는 좀 요상한 자료구조가 담긴다. Array(Dictionary) 이런 형태다.

그리고 문자열에서 유추할 수 있듯, sticker와 background가 있는데, 말그래도 배경과 스티커라고 생각하면 된다.

 

pasteboardOptions 등 외의 코드는 는 이번주내로 다시 알아볼 것이다.

 

중요한 점!

Instagram의 맞춤 URL 스키마를 허용 리스트에 등록

앱에서 Instagram의 맞춤 URL 스키마를 사용하려면 허용 리스트에 추가해야 합니다. 이를 위해서는 앱의 Info.plist에 있는 LSApplicationQueriesSchemes 키에 instagram-stories를 추가합니다.

 

라고 Facebook Developer 에 적혀있다. 따라하면 된다.

Info.plist 에 들어가서 LSApplicationQueriesSchemes를 추가하고 해당 value에 "instagram-stories"를 넣으면 된다.

** value는 Array형태로 넣어야한다

 

안드로이드에서는 manifest에 Internet만 허용해주면 대부분의 url에는 접근이 가능했던 것 같은데, iOS는 따로따로 지정해줘야하나보다... 확실히 apple이 보안/정보 측면에서 강도 높게 설정하고 유지하고 있다고 다시 생각하게 되었다.

 

 

'iOS > SwiftUI' 카테고리의 다른 글

[iOS] SwiftUI 에서 Screen Capture 후 Share  (2) 2021.01.13

사이드 프로젝트를 진행하면서 화면을 캡처한 후에 공유하는 기능이 필요해서 본격적인 개발 전에 기능 구현 정도는 해봐야겠어서 미리 겪어본 것들을 적는다.

확실히 몇년 사이에 구글링만해도 없는 정보가 없는 것 같다. 하지만 SwiftUI로 적용되는 예제나 문서, 글은 확실히 적다...

나 스스로도 나중에 필요할 때, 검색하다 내 블로그를 들어올 수 있도록 작성하는 것이 목표이다.

어쩃든 시작

 

 

우선 Swift로 Screen Capture 하는 법을 찾아보면 UIView를 확장하여 적용된 코드들이 많이 나온다.

하지만 SwiftUI에서는 UIKit을 사용하지 않으려고 하기 때문에, 굳이 굳이 SwiftUI로의 전환을 하는 데에 생각보다 많은 시간을 쏟았다.

 

func takeCapture() -> UIImage {
        var image: UIImage?
        guard let currentLayer = UIApplication.shared.windows.first { $0.isKeyWindow }?.layer else { return UIImage() }

        let currentScale = UIScreen.main.scale

        UIGraphicsBeginImageContextWithOptions(currentLayer.frame.size, false, currentScale)

        guard let currentContext = UIGraphicsGetCurrentContext() else { return UIImage() }

        currentLayer.render(in: currentContext)

        image = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return image ?? UIImage()
    }

우선 화면을 Capture하여 UIImage로 뽑아주는 함수이다.

 

currentLayer 에 UIApplication.shared.keyWindow.layer 로 적용되어 있는 코드들이 많은데 keyWIndow가 deprecate되면서 다른 방법을 찾아보았다.

프로젝트에 중요한 기능이기에 일단 구현하여 실행을 해본 후에 공부하려고 맘 먹었다.(원래 이러면 안되는데... 시간이 부족해서 ㅜㅜ)

 

func saveInPhoto(img: UIImage) {
        UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil)
    }

    func sharePicture(img: UIImage) {
        let av = UIActivityViewController(activityItems: [img], applicationActivities: nil)
        UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
    }

이어서 사진을 사진 iOS App에 저장하는 함수와 UIActivityVC를 사용하여 Share하는 함수이다.

 

이 코드들을 적용해서 캡처된 사진을 보면 사진이 뿌옇게 나오는 현상이 있었다.

그래서 UIImage의 background나 opacity 같은 속성을 건드려야 하나? 라고 생각하고 있던 찰나

버튼을 누르는 순간 바로 캡처가 되는 것인가? 라고 생각되어서 버튼을 누르고 0.5초 뒤에 캡처되게 구현하였다.

 

당연히 DispatchQueue.main.asyncAfter 를 이용해서 딜레이를 주니 정상적으로 캡처가 되었다.

 

TODO: 이번주 내로 저 메소드들에 있는 한 라인/속성 들이 무엇을 의미하는 지 공부해서 수정할 것이다.

 

p.s : 이번달 내로 2020 회고도 작성해보고 싶다... 2020년이 제일 바쁜 해였어서?!

 

+ Recent posts