FRONT/IOS

[ios]네트워크 상태 모니터링 구현 및 WebViewKit 적용

nkm 2024. 11. 27. 15:12
728x90
반응형

 

 

문제 정의

 

네트워크 연결 상태를 감지하고 사용자에게 적절한 피드백을 제공하며, 문제를 해결할 수 있는 UI와 로직을 구현 목표

 

목표

 

1. 네트워크 상태 모니터링 구현

NetworkMonitor를 활용하여 네트워크 상태 변화를 실시간으로 감지.

네트워크가 끊어졌을 때 알림을 보내고 연결 상태를 UI에 반영.

 

2. 에러 화면 제공

네트워크 연결 문제가 발생했을 때 NetworkErrorView를 통해 사용자에게 상태를 알리고 해결 방안을 제공.

 

3. 사용자 동작 지원

“다시 시도하기” 버튼을 통해 WebView를 다시 로드하거나 초기 화면으로 복귀.

“웹으로 보기” 버튼을 통해 Safari 또는 Chrome에서 URL을 열어 문제를 해결.

 

구현 과정

 

1. 네트워크 상태 모니터링

 

설명:

Apple의 Network 프레임워크를 사용하여 네트워크 상태를 감지합니다. NWPathMonitor를 활용하여 네트워크 연결 상태 변화를 감지하고, 상태에 따라 알림(NotificationCenter)을 전송합니다.

 

코드 및 주석

 

import Network



class NetworkMonitor {

    static let shared = NetworkMonitor() // 싱글턴으로 네트워크 상태를 관리

    private let monitor = NWPathMonitor() // 네트워크 상태를 감지하는 객체

    private let queue = DispatchQueue.global(qos: .background) // 백그라운드에서 네트워크 상태를 감지

    var isConnected: Bool = true // 네트워크 연결 상태를 저장



    private init() {}



    // 네트워크 상태 감지를 시작하는 메서드

    func startMonitoring() {

        monitor.start(queue: queue) // 감지 시작

        monitor.pathUpdateHandler = { [weak self] path in

            let isConnected = path.status == .satisfied // 연결 상태 확인

            if isConnected != self?.isConnected {

                self?.isConnected = isConnected

                DispatchQueue.main.async { // UI 업데이트를 위해 메인 스레드에서 실행

                    if isConnected {

                        NotificationCenter.default.post(name: .networkConnected, object: nil)

                    } else {

                        NotificationCenter.default.post(name: .networkDisconnected, object: nil)

                    }

                }

            }

        }

    }



    // 네트워크 상태 감지를 중지하는 메서드

    func stopMonitoring() {

        monitor.cancel()

    }

}



// 네트워크 연결 상태에 대한 알림 이름 정의

extension Notification.Name {

    static let networkDisconnected = Notification.Name("networkDisconnected")

    static let networkConnected = Notification.Name("networkConnected")

}

 

2. 네트워크 에러 화면 구현

 

설명:

UIView를 상속받아 NetworkErrorView를 구현하였습니다. 이 뷰는 네트워크 연결이 끊어졌을 때 사용자에게 메시지와 버튼을 제공하여 문제를 해결하도록 돕습니다.

 

구성 요소

 

1. UILabel: 에러 메시지를 표시.

2. UIButton:

“다시 시도하기” 버튼: WebView를 재로드하거나 초기 화면으로 복귀.

“웹으로 보기” 버튼: Safari 또는 Chrome으로 URL 열기.

 

코드 및 주석

 

import UIKit



class NetworkErrorView: UIView {

    // 에러 메시지를 표시하는 레이블

    private let errorLabel: UILabel = {

        let label = UILabel()

        label.text = "네트워크 연결이 원활하지 않습니다."

        label.textAlignment = .center

        label.font = UIFont.boldSystemFont(ofSize: 18)

        return label

    }()

    

    // 다시 시도하기 버튼

    private let retryButton: UIButton = {

        let button = UIButton(type: .system)

        button.setTitle("다시 시도하기", for: .normal)

        button.backgroundColor = .systemBlue

        button.setTitleColor(.white, for: .normal)

        button.layer.cornerRadius = 8

        return button

    }()

    

    // 웹으로 보기 버튼

    private let webViewButton: UIButton = {

        let button = UIButton(type: .system)

        button.setTitle("웹으로 보기", for: .normal)

        button.setTitleColor(.systemBlue, for: .normal)

        return button

    }()

    

    // 초기화

    override init(frame: CGRect) {

        super.init(frame: frame)

        setupUI()

        setupActions()

    }



    required init?(coder: NSCoder) {

        super.init(coder: coder)

        setupUI()

        setupActions()

    }



    // UI를 구성하는 메서드

    private func setupUI() {

        self.addSubview(errorLabel)

        self.addSubview(retryButton)

        self.addSubview(webViewButton)



        errorLabel.translatesAutoresizingMaskIntoConstraints = false

        retryButton.translatesAutoresizingMaskIntoConstraints = false

        webViewButton.translatesAutoresizingMaskIntoConstraints = false



        NSLayoutConstraint.activate([

            errorLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),

            errorLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 200),



            retryButton.centerXAnchor.constraint(equalTo: self.centerXAnchor),

            retryButton.topAnchor.constraint(equalTo: errorLabel.bottomAnchor, constant: 20),



            webViewButton.centerXAnchor.constraint(equalTo: self.centerXAnchor),

            webViewButton.topAnchor.constraint(equalTo: retryButton.bottomAnchor, constant: 10)

        ])

    }



    // 버튼 액션 설정

    private func setupActions() {

        retryButton.addTarget(self, action: #selector(retryButtonTapped), for: .touchUpInside)

        webViewButton.addTarget(self, action: #selector(webViewButtonTapped), for: .touchUpInside)

    }

    

    // 다시 시도하기 버튼이 눌렸을 때 호출

    @objc private func retryButtonTapped() {

        if let parentViewController = self.findParentViewController() as? HomeViewController {

            parentViewController.reloadWebView()

            self.removeFromSuperview()

        }

    }



    // 웹으로 보기 버튼이 눌렸을 때 호출

    @objc private func webViewButtonTapped() {

        if let url = URL(string: "https://www.example.com") {

            UIApplication.shared.open(url, options: [:])

        }

    }



    // 부모 ViewController를 찾는 메서드

    private func findParentViewController() -> UIViewController? {

        var parentResponder: UIResponder? = self

        while parentResponder != nil {

            parentResponder = parentResponder?.next

            if let viewController = parentResponder as? UIViewController {

                return viewController

            }

        }

        return nil

    }

}

 

3. HomeViewController에서 WebView 처리

 

설명:

네트워크 상태 변화에 따라 WebView를 다시 로드하거나 초기화하며, 네트워크 상태를 감지해 NetworkErrorView를 표시하고 제거합니다.

 

코드 및 주석

import UIKit

@preconcurrency import WebKit



class HomeViewController: UIViewController {

    @IBOutlet weak var webView: WKWebView!



    override func viewDidLoad() {

        super.viewDidLoad()



        setupWebView()

        loadWebPage()

        setupNetworkMonitoring()

    }



    // WebView 초기화

    private func setupWebView() {

        webView.navigationDelegate = self

    }



    // 초기 웹 페이지 로드

    private func loadWebPage() {

        let url = URL(string: "https://www.example.com")!

        webView.load(URLRequest(url: url))

    }



    // 네트워크 상태 모니터링 설정

    private func setupNetworkMonitoring() {

        NotificationCenter.default.addObserver(self,

                                               selector: #selector(showNetworkErrorView),

                                               name: .networkDisconnected,

                                               object: nil)

        NotificationCenter.default.addObserver(self,

                                               selector: #selector(removeNetworkErrorView),

                                               name: .networkConnected,

                                               object: nil)

        NetworkMonitor.shared.startMonitoring()

    }



    // 네트워크 에러 화면 표시

    @objc private func showNetworkErrorView() {

        let errorView = NetworkErrorView(frame: self.view.bounds)

        self.view.addSubview(errorView)

    }



    // 네트워크 에러 화면 제거

    @objc private func removeNetworkErrorView() {

        self.view.subviews.forEach {

            if $0 is NetworkErrorView {

                $0.removeFromSuperview()

            }

        }

    }



    // WebView 다시 로드

    func reloadWebView() {

        if let currentURL = webView.url {

            webView.load(URLRequest(url: currentURL))

        } else {

            let url = URL(string: "https://www.example.com")!

            webView.load(URLRequest(url: url))

        }

    }

}

 

문제 및 해결

 

1. NetworkErrorView 중복 추가 문제

문제: 네트워크 연결 끊김 시 NetworkErrorView가 중복으로 추가되는 현상 발생.

해결: view.subviews.contains를 사용하여 중복 추가를 방지.

 

2. WebView 로드 실패 시 초기 URL 처리

문제: WebView의 URL이 nil일 경우 로드 실패.

해결: reloadWebView에서 URL이 nil이면 초기 URL을 설정.

 

3. Safari와 Chrome 연동 문제

문제: “웹으로 보기” 버튼에서 Chrome이 설치되어 있어도 Safari에서 열리는 현상.

해결: Info.plistgooglechrome URL 스키마를 추가.

 

 

728x90
반응형