티스토리 뷰

UI 살짝 바꾼 것 뿐인데 매번 새로 빌드에서 확인하기 귀찮지 않으신가요? iOS에서 Hot Reloading 기능을 이용해 변경사항을 바로 시뮬레이터에서 확인할 수 있는 라이브러리가 있어 소개해드리려고 합니다. InjectIII라는 라이브러리로 사용법도 엄청 간단해요!

 

UIKit에서도 SwiftUI에서도 사용 가능합니다.

 

GitHub - johnno1962/InjectionIII: Re-write of Injection for Xcode in (mostly) Swift

Re-write of Injection for Xcode in (mostly) Swift. Contribute to johnno1962/InjectionIII development by creating an account on GitHub.

github.com

 

먼저 UIKit 프로젝트를 기준으로 설명드리겠습니다.

1. 환경변수 설정


먼저 환경번수를 설정해주어야하는데요, 

TARGETS > Build Settings > Linking > Other Linker Flags에서 Debug모드에 변수를 추가하고 값 부분에

-Xlinker -interposable

를 입력해줍니다!

 

 

2. AppDelegate 설정


@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
 // ✅ 여기부터       
#if DEBUG
        Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
#endif
 // ✅ 여기까지   
        
        return true
    }
    
    //...

AppDelegate > didFinishLaunching 부분에 #if DEBUG ~ #endif 부분을 작성해줍니다.

 

3. UIViewController Extension 추가


extension UIViewController {
    @objc func injected() {
        viewDidLoad()
    }
}

위 코드를 프로젝트에 추가해줍니다.

 

4. InjectIII 프로그램 다운


InjectIII를 이용하기 위해선 맥에서 사용하는 추가적인 프로그램이 필요한데요, Mac App Store에서 다운 받으실 수 있습니다!

다운받고 해당 프로그램을 실행시키면 상태창에 아래와 같은 아이콘이 생깁니다.

이 아이콘을 클릭하면 Open project라는 버튼이 맨 위에 있는데요, 이걸 클릭해주시고 사용하고자 하는 프로젝트 디렉토리를 선택해주시면 됩니다. 이때, .xcodeproj 파일이 들어있는 디렉토리를 선택해주세요!

 

5. 프로젝트 빌드


프로젝트를 빌드해보셨을 때

아이콘이 주황색으로 바뀌고

콘솔에 위와 같은 문구가 뜨면 연결이 완료된 것입니다!

 

UI값을 바꾸고 저장버튼(cmd+s)를 누를때마다 시뮬레이터가 재빌드 없이 변경되는 것을 확인하실 수 있습니다!

 

⚠️ 사용하는 데이터 형식 자체가 바뀌거나 하는 경우에는 재빌드가 필요할 수도 있습니다. 변경이 잘 안될땐 한번씩 재빌드를 해주세요!

 

+SwiftUI에 적용


SwiftUI에 preview 뷰어가 있긴 하지만 많이 불안정하죠.. 또 시뮬레이터에서 바로 보고싶을 때, InjectIII를 적용해볼 수 있을 것 같습니다.

 

0. 우선 UIKit 방법의 1번(환경변수설정) 4번(프로그램설치)은 동일하게 진행해주세요!

 

1. 그 다음 새파일을 만들고 아래 코드를 그대로 복붙해 붙여주세요.

#if DEBUG
private var loadInjection: () = {
    guard objc_getClass("InjectionClient") == nil else { return }
    #if os(macOS) || targetEnvironment(macCatalyst)
    let bundleName = "macOSInjection.bundle"
    #elseif os(tvOS)
    let bundleName = "tvOSInjection.bundle"
    #elseif targetEnvironment(simulator)
    let bundleName = "iOSInjection.bundle"
    #else
    let bundleName = "maciOSInjection.bundle"
    #endif
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/"+bundleName)!.load()
}()

import Combine
import SwiftUI

public let injectionObserver = InjectionObserver()

public class InjectionObserver: ObservableObject {
    @Published var injectionNumber = 0
    var cancellable: AnyCancellable? = nil
    let publisher = PassthroughSubject<Void, Never>()
    init() {
        cancellable = NotificationCenter.default.publisher(for:
            Notification.Name("INJECTION_BUNDLE_NOTIFICATION"))
            .sink { [weak self] change in
            self?.injectionNumber += 1
            self?.publisher.send()
        }
    }
}

extension View {
    public func eraseToAnyView() -> some View {
        _ = loadInjection
        return AnyView(self)
    }
    public func onInjection(bumpState: @escaping () -> ()) -> some View {
        return self
            .onReceive(injectionObserver.publisher, perform: bumpState)
            .eraseToAnyView()
    }
}
#else
extension View {
    public func eraseToAnyView() -> some View { return self }
    public func onInjection(bumpState: @escaping () -> ()) -> some View {
        return self
    }
}
#endif

 

3. 마지막으로 RootView(ContentView로 처음에 생기죠)에 다음과 같이 설정해줍니다.

import SwiftUI

struct ContentsView: View {

// ✅ 추가
#if DEBUG
    @ObservedObject var iO = injectionObserver
#endif

    var body: some View {
        InnerView("Hello, World!")
        .eraseToAnyView() // ✅ 추가
    }
}

완성입니다!

 

만약 리로드되는 순간마다 어떤 작업을 하고싶으면 

import SwiftUI

struct ContentsView: View {

// ✅ 추가
#if DEBUG
    @ObservedObject var iO = injectionObserver
#endif

    var body: some View {
        InnerView("Hello, World!")
        .onInjection {  // ✅ 추가
            print("reload") // ✅ 원하는 동작
        }
    }
}

.eraseToAnyView 대신 .onInjection 메서드를 써주시면 됩니다!

 

⚠️ 오류가 나거나 작동하지 않을 때는 아래와 같은 방법을 시도해보세요!
1. cmd+shift+K(빌드클린)
2. File > Pakages > Reset Pakage Caches
3. Deriven Data 삭제
💬 며칠 사용해보니 SwiftUI에서는 런타임 오류도 많이 나고 생각한 것 만큼 잘 작동하지 않더라구요... 프리뷰만큼이나 불안정하고 자주 재빌드를 해줘야해서 불편합니다. 다만 단일 뷰 환경이 아닌 시뮬레이터에서 앱 전체에서 변경사항을 확인해보기는 좋았어요. 기본 설정만 해두고 필요할때만 써야겠습니다..!

읽어주셔서 감사합니다!

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함