관련 글
소개
UIDelegate에서 구현해야되는 자바스크립트 경고창 (Alert, Confirm, Prompt) 표시는 어떻게 해야 할까요?
- Coordinator 클래스를 만들고
WKUIDelegate
를 준수(conform)- 부모 뷰 (
WebViewRP
)를 참조하는 변수를 정의하고 이 변수를 통해 코디네이터와 통신
- 부모 뷰 (
- 코디네이터 클래스 안에 자바스크립트 경고창 표시와 관련된 함수를 오버라이딩 및 정의
- 제목 메시지 및 Alert 표시 여부는
@State
,@Binding
변수들을 통해 전달- 참고)
@State
및@Binding
변수들은ObservableObject
뷰모델의@Published
로 대체할 수 있습니다.
- 참고)
- Alert의 확인버튼, Confirm의 예/아니오 버튼, Prompt의 텍스트 필드 및 전송/취소 버튼이 클릭되었을 때 해야 할 작업등은
(파라미터) -> Void
형태의 클로저 함수로 전달- 참고)
ObservableObject
뷰모델을 사용하는 경우 뷰모델에 해당 클로저 함수를 저장하도록 지정할 수 있습니다.
- 참고)
ObservableObject
를 사용하여 분리한 코드는 여기에서 볼 수 있습니다.
코드 (@State, @Binding 사용)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct ContentView: View { | |
@State private var showAlert = false | |
@State private var showConfirm = false | |
@State private var showPrompt = false | |
@State private var alertMessage = "" | |
@State private var promptInput = "" | |
@State private var alertHandler: (() -> Void)? | |
@State private var confirmHandler: ((Bool) -> Void)? | |
@State private var promptHandler: ((String?) -> Void)? | |
var body: some View { | |
WebViewRP( | |
showAlert: $showAlert, | |
showConfirm: $showConfirm, | |
showPrompt: $showPrompt, | |
alertMessage: $alertMessage, | |
alertHandler: $alertHandler, | |
confirmHandler: $confirmHandler, | |
promptHandler: $promptHandler | |
) | |
.alert(alertMessage, isPresented: $showAlert) { | |
Button("OK", role: .none) { | |
alertHandler?() | |
} | |
} | |
.alert(alertMessage, isPresented: $showConfirm) { | |
Button("No", role: .cancel) { | |
confirmHandler?(false) | |
} | |
Button("Yes", role: .none) { | |
confirmHandler?(true) | |
} | |
} | |
.alert(alertMessage, isPresented: $showPrompt) { | |
TextField("Prompt", text: $promptInput) | |
Button("제출", role: .none) { | |
promptHandler?(promptInput) | |
promptInput = "" | |
} | |
Button("취소", role: .cancel) { | |
promptHandler?(nil) | |
promptInput = "" | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct WebViewRP: UIViewRepresentable { | |
typealias UIViewType = WKWebView | |
@Binding var showAlert: Bool | |
@Binding var showConfirm: Bool | |
@Binding var showPrompt: Bool | |
@Binding var alertMessage: String | |
@Binding var alertHandler: (() -> Void)? | |
@Binding var confirmHandler: ((Bool) -> Void)? | |
@Binding var promptHandler: ((String?) -> Void)? | |
func makeUIView(context: Context) -> WKWebView { | |
let webView = WKWebView() | |
let url = URL(string: "https://testpages.herokuapp.com/styled/alerts/alert-test.html")! | |
webView.load(URLRequest(url: url)) | |
webView.uiDelegate = context.coordinator | |
return webView | |
} | |
func updateUIView(_ uiView: WKWebView, context: Context) {} | |
func makeCoordinator() -> Coordinator { | |
Coordinator(self) | |
} | |
class Coordinator: NSObject, WKUIDelegate { | |
let parent: WebViewRP | |
init(_ parent: WebViewRP) { | |
self.parent = parent | |
} | |
func webView( | |
_ webView: WKWebView, | |
runJavaScriptAlertPanelWithMessage message: String, | |
initiatedByFrame frame: WKFrameInfo | |
) async { | |
await withCheckedContinuation { continuation in | |
DispatchQueue.main.async { | |
self.parent.showAlert = true | |
self.parent.alertMessage = message | |
// 대기(await)중이던 async 함수를 재개한다. | |
self.parent.alertHandler = { | |
continuation.resume() | |
} | |
} | |
} | |
} | |
// Confirm창: completionHandler 전송으로 구현 (간단) | |
func webView( | |
_ webView: WKWebView, | |
runJavaScriptConfirmPanelWithMessage message: String, | |
initiatedByFrame frame: WKFrameInfo, | |
completionHandler: @escaping @MainActor (Bool) -> Void | |
) { | |
DispatchQueue.main.async { | |
self.parent.showConfirm = true | |
self.parent.alertMessage = message | |
self.parent.confirmHandler = completionHandler | |
} | |
} | |
// Prompt창: async/await로 구현 (복잡) | |
func webView( | |
_ webView: WKWebView, | |
runJavaScriptTextInputPanelWithPrompt prompt: String, | |
defaultText: String?, | |
initiatedByFrame frame: WKFrameInfo | |
) async -> String? { | |
return await withCheckedContinuation { continuation in | |
DispatchQueue.main.async { | |
self.parent.showPrompt = true | |
self.parent.alertMessage = prompt | |
self.parent.promptHandler = { inputValue in | |
continuation.resume(returning: inputValue) | |
} | |
} | |
} | |
} | |
} | |
} |
CompletionHandler를 사용하는 메서드에서 completionHandler의 역할
웹 페이지의 자바스크립트 실행 부분에서 경고창이 뜨면 다음 명령을 기다리게 됩니다. iOS 앱에서 Handler 함수가 실행되면 다음에 해야 할 자바스크립트 작업을 이어서 실행합니다.
이 핸들러 함수를 컨텐트 뷰에 전달하고 SwiftUI의 경고창에서 버튼을 눌렀을 때 전달받은 핸들러를 실행하면 웹페이지의 자바스크립트가 다음 작업을 실행하게 되는 것입니다.
async/await 메서드에서 self.parent.alertHandler = {…} 부분의 동작 원리
핵심 개념
alertHandler는 @Binding
으로 전달된 클로저입니다. Swift의 클로저는 코드 블록을 캡처하고, 나중에 실행될 수 있는 “일급 객체”입니다. 이 클로저를 continuation.resume()
로 연결하여, JavaScript alert()
가 비동기적으로 처리될 수 있도록 합니다.
동작 원리
- alertHandler 초기 설정
parent.alertHandler
는 UIViewRepresentable의 SwiftUI View와 연결된 상태 변수입니다.- 초기값은 비어 있지만,
webView(_:runJavaScriptAlertPanelWithMessage:initiatedByFrame:)
호출 시 새로운 클로저를 할당합니다.
- withCheckedContinuation 사용
- Swift Concurrency에서
withCheckedContinuation
은 콜백 기반 코드를async/await
로 변환하는 데 사용됩니다. - JavaScript
alert()
이벤트는 비동기로 처리되므로, Continuation을 사용해 비동기 흐름을 제어합니다.
- Swift Concurrency에서
- 클로저 저장
- JavaScript
alert()
호출 시self.parent.alertHandler
에 다음 동작을 정의한 클로저를 할당합니다:
self.parent.alertHandler = { continuation.resume() }
- 이 클로저는 SwiftUI의 Alert의 “OK” 버튼과 연결되어, 버튼이 눌릴 때 실행됩니다.
- JavaScript
- Alert 표시
showAlert
를true
로 설정하면 SwiftUI에서 Alert이 표시됩니다.- Alert의 “OK” 버튼을 누르면
alertHandler()
가 실행되고, Continuation이 재개됩니다.
- Continuation 재개
continuation.resume()
가 호출되면,await
가 완료되고webView(_:runJavaScriptAlertPanelWithMessage:)
의 호출이 끝납니다.- 이로써 비동기 흐름이 정상적으로 처리됩니다.
0개의 댓글