티스토리 뷰

스위프트/SwiftUI

[SwiftUI] - WebView 구현하기

강철곰탱이 2024. 12. 2. 17:31

 

WebView를 구현해보려고 한다.

 

📖 목차

1. WebView를 왜 사용해야하는가?
2. WebView 구현 종류 및 사용법
3. WebView에 대한 고찰

1. WebView를 왜 사용해야하는가?

 

 

 WebView를 사용하는 이유?

 

네이티브 앱이 있는데 왜 굳이 WebView를 사용해야하는가에 대한 생각이 있었다.

WebView를 사용하는가장 큰 이유는 두 가지라고 생각한다.

 

1️⃣ 크로스 플랫폼 지원

 

웹뷰는 한 번 작성된 웹 콘텐츠를 iOS, Android에서 동일하게 사용할 수 있도록 지원한다.

그래서 플랫폼별로 별도의 네이티브 코드를 작성하지 않아도 되는 장점이 있다.

 

 

2️⃣ 배포 및 업데이트 용이성

 

iOS 네이티브 코드로 작성하면 앱 스토어의 심사 과정을 계속 거쳐서 통과받아야하는 단점이 있다.

근데 WebView를 사용하면 앱 스토어 심사과정을 거치지 않고도 서버에서 바로 업데이트할 수 있어, 빠르고 유연한 수정 및 배포가 가능하여 유지보수에 좋다.

 


 

 

정리하자면, 웹뷰는 크로스 플랫폼 지원앱 스토어 심사 없이 업데이트 가능한 점에서 중요하다.

이를 통해 외부 웹 콘텐츠를 앱에 쉽게 통합하고, 빠르게 수정 및 배포하며, 사용자 경험을 향상시킬 수 있다. 개발 효율성운영 유연성이 웹뷰를 사용하는 주요 이유라고 생각한다.

 


2. WebView 구현 종류 및 사용법

 

 

찾아보니 WebView를 구현하는 방법은 총 4가지가 있다.

 

  1. UIWebView(deprecated)
  2. 설치된 앱 직접 사용
  3. SFSafariViewController
  4. WKWebView

 

4가지가 어떻게 다른지 확인하기 위해 같은 예제로 확인해보려고 한다.

iOS에서 텍스트 입력을 받아 URL로 검색창을 열어주는 예제를 구현해보자!

 

예제에 사용하는 기본 뷰 코드는 다음과 같다.

struct ContentView: View {
    @State private var searchText: String = "" // 입력 필드의 텍스트 상태 
    
    var body: some View {
        VStack(spacing: 16) {
            TextField("네이버에서 검색할 내용을 입력하세요", text: $searchText)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.horizontal)
            
            Button(action: {
                shouldShowWebView = true
            }) {
                Text("검색하기")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            .padding(.horizontal)
        }
    }
}

 

 


 

UIWebView

 

 

UIWebView | Apple Developer Documentation

A view that embeds web content in your app.

developer.apple.com

 

UIWebView는 deprecated되었으니 예제는 구현하지 않고 넘어간다.

왜 deprecated되었을까 찾아보다가 다음과 같은 문제가 있었다는 걸 알게되었다.

 

1️⃣ 성능 문제

UIWebView는 HTML 렌더링 및 자바스크립트 실행 속도가 느렸고, UIWebView는 메모리 사용량이 많고 효율적이지 않아, 특히 복잡한 웹 콘텐츠를 표시할 때 앱이 자주 충돌하거나 메모리 경고를 발생시켰다고 한다.

 

2️⃣ 멀티프로세스 아키텍처 미지원

 

UIWebView는 단일 프로세스에서 실행되며, 앱의 메인 프로세스에서 모든 작업을 처리했다. 

UIWebView와 앱이 단일 프로세스에서 실행되기 때문에 UIWebView가 어떤 문제로 다운되면 앱도 같이 다운되는 문제가 있었다.

 

 

=> 이런 문제를 해결하기 위해서 다른 WebView 방식을 지원하게 된 것 같다.

 


 

설치된 앱 직접 사용

 

사용자가 기본 브라우저로 설정해놓은 앱을 직접 실행해서 보여주는 방식이다.

 

struct ContentView: View {
    @State private var searchText: String = "" // 검색어를 저장하는 상태 변수

    var body: some View {
        VStack(spacing: 16) {
            TextField("네이버에서 검색할 내용을 입력하세요", text: $searchText)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.horizontal)

            Button(action: {
                if let encodedSearchText = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
                       let url = URL(string: "https://search.naver.com/search.naver?query=\(encodedSearchText)") {
                        UIApplication.shared.open(url)
                    }
            }) {
                Text("검색하기")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            .padding(.horizontal)
        }
    }
}

 

UIApplication.shared.open을 사용해서 해당 url을 기본 브라우저 앱으로 보여준다.

 

 


 

SFSafariViewController

 

 

Safari와 거의 동일한 UI 및 기능을 제공하여 사용자가 친숙하게 느낄 수 있고, 기본적으로 제공하는 기능들이 있어서 쉽게 구현할 수 있다. 하지만 커스텀이 안되고 앱과 데이터를 공유할 수 없다는 단점이 있다.

 

🔎 특징

  • Safari와 거의 동일한 UI 및 기능을 제공
  • Safari에서 저장된 쿠키자격 증명을 사용할 수 있음
  • 앱과 데이터를 공유할 수 없음
  • 커스텀 불가

 

 

네이버 검색하는 url은 다음과 같다.

query= 뒤에 텍스트가 들어가면 해당 텍스트에 따른 검색결과를 보여주는 네이버 웹뷰를 볼 수 있다.

 

https://search.naver.com/search.naver?query=

 

import SwiftUI
import SafariServices

struct ContentView: View {
    @State private var searchText: String = "" // 검색어를 저장하는 상태 변수
    @State private var safariURL: URL? // Safari를 열 URL 저장
    @State private var isShowingSafari: Bool = false // SafariView 표시 여부

    var body: some View {
        VStack(spacing: 16) {
            TextField("네이버에서 검색할 내용을 입력하세요", text: $searchText)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.horizontal)

            Button(action: {
                if let encodedSearchText = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
                   let url = URL(string: "https://search.naver.com/search.naver?query=\(encodedSearchText)") {
                    safariURL = url
                    isShowingSafari = true
                }
            }) {
                Text("검색하기")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            .padding(.horizontal)
            .sheet(isPresented: $isShowingSafari) {
                if let safariURL {
                    SafariView(url: safariURL)
                }
            }
        }
    }
}

 

searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)는 URL에 포함될 수 없는 특수 문자들을 적절히 인코딩하는 작업으로, 네트워크 요청에서 올바른 URL을 생성하기 위해 필수이다.

 

✏️ 특수 문자 처리

 

 

struct SafariView: UIViewControllerRepresentable {
    let url: URL

    func makeUIViewController(context: Context) -> SFSafariViewController {
        return SFSafariViewController(url: url)
    }

    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
        // 업데이트 로직 필요 없음
    }
}

 

SafariView는 SwiftUI 뷰이며, UIKit의 SFSafariViewController를 SwiftUI에서 사용할 수 있게 만들어준다.

 

✏️ UIViewControllerRepresentable 프로토콜

 

UIViewControllerRepresentable 프로토콜을 구현하면 UIKit의 UIViewController를 SwiftUI에서 사용 가능하게 만든다.

 

두 가지 필수 메서드가 필요하다:

 

  • makeUIViewController: UIKit의 UIViewController를 생성
  • updateUIViewController: SwiftUI가 UIViewController를 업데이트해야 할 때 호출

 

 

 


 

WKWebView

 

 

현재 가장 많이 사용하고 있는 방식이라고 할 수 있다.

 

WKWebView는 앱의 UI에 Web 콘텐츠를 통합시켜 주는 View이다.

Safari의 렌더링 엔진(WebKit)을 사용하며, UIWebView의 성능 및 기능적 한계를 개선한 대체 기술로 iOS 8부터 도입되었다.

 

🔎 특징

  • 커스텀 브라우저 UI를 구현할 수 있음
  • 앱과 데이터 교환 가능
  • UIWebView에 비해 더 나은 메모리 관리와 로딩 속도를 제공
  • 멀티 프로세스 지원

 

 

WKWebView의 가장 큰 장점은 커스텀 가능앱과 데이터 교환 가능, 멀티 프로세스 지원인 것 같다.

장점이 너무 많네,,, ㅎㅎ

 

멀티 프로세스를 지원하기 때문에 WKWebView가 앱 프로세스와 독립적으로 실행되어 WKWebView가 오류로 다운되는 일이 있어도 앱은 다운되지 않는다.

 

 

WebKit를 사용하기 위해서는, 먼저 프로젝트에 추가해야 한다.

프로젝트 설정 파일에서 [Build Phases -> Link Binary With Libraries] 로 이동해 +를 눌러 WebKit.framework를 추가한다.

 

 

shouldShowWebView 변수를 만들어서 텍스트를 입력하고 버튼을 누르면 shouldShowWebView를 true로 만들어준다.

 

struct ContentView: View {
    @State private var searchText: String = "" // 검색어를 저장하는 상태 변수
    @State private var wkURL: URL? 
    @State private var shouldShowWebView: Bool = false 

    var body: some View {
        VStack(spacing: 16) {
            TextField("네이버에서 검색할 내용을 입력하세요", text: $searchText)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.horizontal)

            Button(action: {
                if let encodedSearchText = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
                   let url = URL(string: "https://search.naver.com/search.naver?query=\(encodedSearchText)") {
                    wkURL = url
                    shouldShowWebView = true
                }
            }) {
                Text("검색하기")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            .padding(.horizontal)
            .sheet(isPresented: $shouldShowWebView) {
                if let wkURL {
                    WebView(url: wkURL)
                }
            }
        }
    }
}

 

WebView 구조체는 SwiftUI에서 UIKit의 WKWebView를 통합하기 위해 설계된 코드이다. UIViewRepresentable 프로토콜을 채택하여 SwiftUI와 UIKit의 뷰를 연결한다. 

struct WebView: UIViewRepresentable {
    let url: URL

    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        uiView.load(request)
    }
}

 

✏️ UIViewRepresentable 프로토콜

UIViewRepresentable은 UIKit의 뷰(UIView)를 SwiftUI에서 사용할 수 있도록 해주는 프로토콜이다.


이를 구현하려면 두 가지 필수 메서드를 정의해야 한다:

  • makeUIView(context:): 초기화 및 뷰 생성을 담당.
  • updateUIView(_:context:): 뷰를 업데이트할 때 호출.

 

 

 

 

 

sheet가 화면을 꽉채우지는 못해서 .sheet를 .fullScreenCover로 변경하여 화면 전체를 채우도록 설정하였다.

 

 

 

어라 뒤로가기가 안되는데...?

 

해결하기 위해 네비바로 연결하여 네이버 웹뷰가 다음화면에서 나오도록 구현하였다. 그러면 뒤로가기 누르면 검색화면으로 돌아올 수 있기 때문이다.

 

struct ContentView: View {
    @State private var searchText: String = "" // 검색어를 저장하는 상태 변수

    var body: some View {
        NavigationStack {
            VStack(spacing: 16) {
                TextField("네이버에서 검색할 내용을 입력하세요", text: $searchText)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding(.horizontal)
                
                NavigationLink(destination: {
                    if let encodedSearchText = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
                        let searchURL = "https://search.naver.com/search.naver?query=\(encodedSearchText)"
                        WebViewScreen(url: URL(string: searchURL)!)
                    } else {
                        Text("유효하지 않은 검색어입니다.")
                    }
                }) {
                    Text("검색하기")
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                }
                .padding(.horizontal)
            }
        }
    }
}

 

WebView를 보여줄 수 있는 다른 화면 WebViewScreen을 만들어준다.

그리고 WebView를 넣어주면 해당 url에 따른 WKWebView를 사용해 웹뷰를 보여줄 수 있다.

struct WebViewScreen: View {
    let url: URL

    var body: some View {
        VStack {
            WebView(url: url)
                .edgesIgnoringSafeArea(.all) // WebView가 화면 전체를 차지하도록 설정
        }
        .navigationTitle("검색 결과")
        .navigationBarTitleDisplayMode(.inline) // 제목을 가운데 정렬
    }
}

 

 

이렇게 구현하면 네비바 부분은 네이티브에서 만든 것이기 때문에 마음대로 커스텀이 가능한 걸 확인할 수 있다.

 


 

정리

 

 

구현 방법 설명 특징
UIWebView 더 이상 지원되지 않음 (Deprecated). 사용 지양. 성능과 보안 이슈 있음.
설치된 브라우저 앱 사용 기본 브라우저 앱(Safari 등)을 통해 URL을 열어줌. 앱 외부에서 열리며, 앱과 상호작용 불가능
SFSafariViewController 앱 내에서 Safari 경험 제공. Safari와 유사한 UI. 사용자 쿠키와 저장된 데이터를 활용. 앱과 상호작용 불가능.
WKWebView Apple 권장 WebView. 고성능, 최신 기술 지원. 완전히 커스텀 가능. JavaScript 및 HTML 렌더링 가능. 앱과 상호작용 가능

 


3.  WebView에 대한 고찰

 

 

어떤 WebView 구현 방식을 사용하는게 좋을까?

 

위에서만 봤을때에 deprecated된 UIWebView를 제외하고 3가지 중 어떤걸 프로젝트에 적용하는게 좋을까?

=> 해당 프로젝트의 요구 사항에 따라 적합한 방법을 선택하면 되기 때문에 이건 정답이 없다.

 

웹뷰에서 표시하려는 내용, 앱과의 상호작용 필요성, 커스터마이징 요구 사항에 따라 적합한 방법을 선택하는 것이 중요하다고 생각한다.

 

  • 앱과의 상호작용 및 커스터마이징이 중요하다면 => WKWebView를 선택
  • 간단히 웹 페이지를 표시하고 기본적인 브라우징 기능만 필요하다면 => SFSafariViewController를 사용
  • 앱 외부에서 브라우저를 통해 웹 콘텐츠를 열어도 무방하다면 => 설치된 브라우저 앱을 사용

 

 

프로젝트에서 WebView를 어떤 View에 적용하는게 좋을까?

 

현재 진행하고 있는 지출관리 앱 프로젝트에서 기능은 다음과 같다.

 

  • 지출관리
  • 채팅
  • 피드
  • 사용자 설정

 

이 중에서 어떤걸 웹뷰로 사용하는게 좋을지 생각해보자.

 

물론 "무조건 웹뷰를 사용해야 한다" 이건 아니다. 웹뷰를 사용했을 때의 장점으로 사용여부를 판단해야 한다.

 

 

1. 지출관리

 

  • 역할: 사용자가 지출 내역을 입력하고 관리.
  • WebView 사용 여부: ❌ 
    • 지출 관리는 일반적으로 앱의 주요 기능이므로 네이티브로 구현하는 것이 적합하다고 판단하였다.
    • 데이터 입력, 수정, 삭제 등과 같은 앱과의 긴밀한 상호작용이 필요하므로 WebView로 구현하면 불편하거나 제한적일 수 있기 때문이다.

 

2. 채팅

 

 

  • 역할: 사용자가 다른 사용자와 소통.
  • WebView 사용 여부: ❌ 
    • 채팅은 실시간 상호작용, 알림, 메시지 상태 관리(읽음/안 읽음 등)가 중요하다.
    • WebView로 구현하면 실시간 기능이 제한될 가능성이 크다.

 

 

3. 피드

 

  • 역할: 지출 관련 피드게시물을 사용자에게 표시.
  • WebView 사용 여부:
    • 실시간 기능이나 복잡한 상호작용이 필요하지 않으므로 WebView로 충분히 구현 가능하다.

 

4. 사용자 설정

 

  • 역할: 알림 설정, 계정 관리.
  • WebView 사용 여부: ❌ 
    • 설정은 앱 내부 기능과 긴밀히 연결되므로 네이티브 UI로 구현하는 것이 적합하다고 판단하였다.

 

 

=> 채팅과 지출 관리 기능 구현에 많은 시간이 소요될 것으로 예상되었기 때문에, 개발 효율성을 높이기 위해 역할을 분담했다. 웹뷰 구현이 가능한 개발자가 피드 기능을 우선적으로 개발하고, 네이티브로 처리할 수 있는 부분은 iOS 개발자가 맡아 진행하기로 했다.

 

이에 따라, 다른 기능과 직접적인 연관이 없는 피드는 WebView로 처리하는 방향으로 결정하게 되었다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
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 31
글 보관함