🍏 ВзаимодСйствиС SwiftUI с Π²Π΅Π±ΠΎΠΌ. Π§Π°ΡΡ‚ΡŒ вторая: Web Navigation

Π’ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΉ ΡΡ‚Π°Ρ‚ΡŒΠ΅ ΠΌΡ‹ создали WebView ΠΈ ΠΏΠΎΠ΄Π³Ρ€ΡƒΠ·ΠΈΠ»ΠΈ Π² Π½Π΅Π³ΠΎ сайт proglib.io. БСгодня займСмся ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΌ интСрфСйсом прилоТСния, Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠ΅ΠΉ ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ΠΌ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ с Π²Π΅Π±-страницы Π² Swift c ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ JavaScript.

Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ прилоТСния

ΠœΡ‹ ΡƒΠΆΠ΅ Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ (ΠΊΠΎΠ΄ доступСн Π½Π° GitHub – ΠΏΡ€ΠΈΠΌ. Ρ€Π΅Π΄.) пСрСчислСниС WebViewNavigationAction, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ описываСт Ρ‚Ρ€ΠΈ дСйствия: Π½Π°Π·Π°Π΄, Π²ΠΏΠ΅Ρ€Π΅Π΄, ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ. Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ для Π½ΠΈΡ… SwiftUI View, ΠΈ Π½Π°Π·ΠΎΠ²Π΅ΠΌ Π΅Π΅ WebNavigationView, Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΠΊΠ½ΠΎΠΏΠΊΠΈ дСйствий. ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ WebView ΠΏΠΎΠ΄Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ Π²Π΅Π±-страницу ΠΈΠ· ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π°, Π° это дСйствиС Π½Π΅ происходит ΠΌΠ³Π½ΠΎΠ²Π΅Π½Π½ΠΎ, Π΄ΠΎΠ±Π°Π²ΠΈΠΌ LoaderView Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŽ прогрСсс Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ.


На создании ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΎΠ³ΠΎ интСрфСйса ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎ ΠΎΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°Ρ‚ΡŒΡΡ Π½Π΅ Π±ΡƒΠ΄Π΅ΠΌ: просто ΠΏΠΎΠΊΠ°ΠΆΠ΅ΠΌ Π΅Π³ΠΎ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ. ΠŸΠΈΡˆΠΈΡ‚Π΅ Π² коммСнтариях, ΠΊΠ°ΠΊΠΈΠ΅ Ρ‚Π΅ΠΌΡ‹ Π²Π°ΠΌ Ρ…ΠΎΡ‚Π΅Π»ΠΎΡΡŒ Π±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ Π² ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΡ… ΡΡ‚Π°Ρ‚ΡŒΡΡ….
LoaderView
import SwiftUI

struct LoaderView: View {
	@State var isSpinCircle = false
    var body: some View {
		ZStack {
			Circle()
				.frame(width: 60, height: 60, alignment: .center)
			VStack {
				Circle()
					.trim(from: 0.3, to: 1)
					.stroke(Color.white, lineWidth: 2)
					.frame(width:50, height: 50)
					.padding(.all, 8)
					.rotationEffect(.degrees(isSpinCircle ? 0 : -360), anchor: .center)
					.animation(Animation.linear(duration: 0.6).repeatForever(autoreverses: false))
					.onAppear {
						self.isSpinCircle = true
					}
			}
		}
	}
}

struct LoaderView_Previews: PreviewProvider {
    static var previews: some View {
        LoaderView()
    }
}
WebNavigationView
import SwiftUI

struct WebNavigationView: View {
    var body: some View {
		VStack {
			Divider()
			HStack(spacing: 10) {
				Divider()
				Button(action: {}, label: {
					Image(systemName: "chevron.left")
						.font(.system(size: 30, weight: .regular))
						.imageScale(.medium)
				})
				Divider()
				Button(action: {}, label: {
					Image(systemName: "chevron.right")
						.font(.system(size: 30, weight: .regular))
						.imageScale(.medium)
				})
				Divider()
				Spacer()
				Divider()
				Button(action: {}, label: {
					Image(systemName: "arrow.clockwise")
						.font(.system(size: 30, weight: .regular))
						.imageScale(.medium)
				})
				Divider()
			}
			.frame(height: 50)
			Divider()
		}
    }
}

struct WebNavigationView_Previews: PreviewProvider {
    static var previews: some View {
        WebNavigationView()
    }
}

Пока дСйствиС наТатия Π½Π° ΠΊΠ½ΠΎΠΏΠΊΡƒ action оставим пустыми. ВСрнСмся ΠΊ Π½ΠΈΠΌ ΠΏΠΎΠ·ΠΆΠ΅.

ΠŸΠ°Ρ€Π° слов ΠΎ ObservableObject
@ObservableObject – это ΠΎΠ±Π΅Ρ€Ρ‚ΠΊΠ°, ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ Ρ€Π°Π·Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΌΠ΅ΠΆΠ΄Ρƒ нСсколькими View. View ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠΎΠ΄ΠΏΠΈΡΡ‹Π²Π°Ρ‚ΡŒΡΡ ΠΈ Π½Π°Π±Π»ΡŽΠ΄Π°Ρ‚ΡŒ Π·Π° измСнСниями этого ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°.

ViewModel

ΠœΡ‹ Π΄ΠΎΠ±Ρ€Π°Π»ΠΈΡΡŒ Π΄ΠΎ самого интСрСсного! Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΏΠΎΠ΄Ρ€ΡƒΠΆΠΈΠΌ всС элСмСнты вмСстС. Для Π½Π°Ρ‡Π°Π»Π° создадим ViewModel ΠΈ ΠΏΠΎΠ΄ΡƒΠΌΠ°Π΅ΠΌ, Ρ‡Π΅Π³ΠΎ ΠΌΡ‹ Ρ…ΠΎΡ‚ΠΈΠΌ ΠΎΡ‚ WebView.

  1. ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π²Π΅Π±-страницы – Π΄ΠΎΠ±Π°Π²ΠΈΠΌ свойство WebTitle;
  2. ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ дСйствия Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ – Π΄ΠΎΠ±Π°Π²ΠΈΠΌ свойство webViewNavigationPublisher;
  3. ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ ΠΊΠΎΠ³Π΄Π° Π½ΡƒΠΆΠ½ΠΎ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ LoaderView – Π΄ΠΎΠ±Π°Π²ΠΈΠΌ свойство isLoaderVisible.
ViewModel
import Foundation
import Combine

class ViewModel: ObservableObject {
	var isLoaderVisible = PassthroughSubject<Bool, Never>();
	var webTitle = PassthroughSubject<String, Never>()
	var webViewNavigationPublisher = PassthroughSubject<WebViewNavigationAction, Never>()
}

Обновим ContentView, ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΠΌ ViewModel ΠΈ состояния isLoaderVisible:

@ObservedObject var viewModel = ViewModel()
@State var isLoaderVisible = false

ΠŸΠΎΡ€Π° Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ вСсь созданный интСрфСйс Π² ContentView

var body: some View {
		ZStack {
			VStack(spacing: 0) {
				WebNavigationView()
				WebView(type: .public, url: "https://proglib.io", viewModel: viewModel)
				
			}
			if isLoaderVisible {
				LoaderView()
			}
		}
}

Π’Π΅ΠΏΠ΅Ρ€ΡŒ вСрнСмся Π² WebView ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ свойство @ObservedObject var viewModel: ViewModel, Π° Ρ‚Π°ΠΊΠΆΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄ makeCoordinator, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ Coordinator для взаимодСйствия с функциями Π΄Π΅Π»Π°Π³Π°Ρ‚Π° ΠΈΠ· WKNavigationDelegate.

func makeCoordinator() -> Coordinator {
        Coordinator(self)
}

НапишСм Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ класса Coordinator.

class Coordinator: NSObject, WKNavigationDelegate {
		var parent: WebView
		var webViewNavigationSubscriber: AnyCancellable? = nil
		
		init(_ webView: WebView) {
			self.parent = webView
		}
		
		deinit {
			webViewNavigationSubscriber?.cancel()
		}
	}

ΠŸΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ Π·Π°Π½ΡΡ‚ΡŒΡΡ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠ΅ΠΉ Π² WebView, ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΠΌ Ρ‡Ρ‚ΠΎ всС Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚. ИзмСним состояниС isLoaderVisible Π½Π° true ΠΈ посмотрим Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Π² preview.

ΠŸΠΎΠ³Ρ€ΡƒΠΆΠ΅Π½ΠΈΠ΅ Π² WKNavigationDelegate

Π”Π°Π²Π°ΠΉΡ‚Π΅ посмотрим, Ρ‡Ρ‚ΠΎ Π½Π°ΠΌ ΠΏΡ€Π΅Π΄Π»Π°Π³Π°Π΅Ρ‚ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ». ΠŸΠ΅Ρ€Π΅ΠΉΠ΄ΠΈΡ‚Π΅ ΠΊ Π΅Π³ΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΡŽ (Jump to Definition Π² Xcode; ΠΈΠ»ΠΈ Π·Π°ΠΆΠΌΠΈΡ‚Π΅ βŒƒβŒ˜ ΠΈ ΠΊΠ»ΠΈΠΊΠ½ΠΈΡ‚Π΅). Как Π²ΠΈΠ΄ΠΈΡ‚Π΅, всС ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΈ Π½Π΅ Ρ‚Ρ€Π΅Π±ΡƒΡŽΡ‚ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ.

Π”Π°Π»Π΅Π΅ ΠΏΡ€ΠΈΠ²Π΅Π΄Π΅ΠΌ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π²Ρ‹ ΠΈΡ… Π·Π½Π°Π»ΠΈ ΠΈ ΠΌΠΎΠ³Π»ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π² своих цСлях.

***
didStartProvisionalNavigation
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся ΠΏΡ€ΠΈ запускС Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ ΠΏΠΎ Π³Π»Π°Π²Π½ΠΎΠΌΡƒ Ρ„Ρ€Π΅ΠΉΠΌΡƒ.

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)

didFailProvisionalNavigation
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° происходит ошибка ΠΏΡ€ΠΈ запускС Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π΄Π°Π½Π½Ρ‹Ρ… Π² Π³Π»Π°Π²Π½Ρ‹ΠΉ Ρ„Ρ€Π΅ΠΉΠΌ.

func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error)

didReceiveServerRedirectForProvisionalNavigation
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° сСрвСр ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ» ΠΏΠ΅Ρ€Π΅Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ для Π³Π»Π°Π²Π½ΠΎΠ³ΠΎ Ρ„Ρ€Π΅ΠΉΠΌΠ°.

func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)

didCommit
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΠΏΠΎΡΡ‚ΡƒΠΏΠ°Ρ‚ΡŒ ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚ для Π³Π»Π°Π²Π½ΠΎΠ³ΠΎ Ρ„Ρ€Π΅ΠΉΠΌΠ°.

func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!)

didFail
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ‚ ошибка ΠΏΡ€ΠΈ ΠΊΠΎΠΌΠΈΡ‚Π΅ (фиксации) Π² Π³Π»Π°Π²Π½ΠΎΠΌ Ρ„Ρ€Π΅ΠΉΠΌΠ΅ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ.

func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error)

didFinish
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° навигация Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°.

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)

didReceive
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° WebView Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΎΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π½Π° запрос Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ.

func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

webViewWebContentProcessDidTerminate
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° Π² WebView ΠΏΡ€Π΅Ρ€Π²Π°Π½Π°.

func webViewWebContentProcessDidTerminate(_ webView: WKWebView)

authenticationChallenge
ΠœΠ΅Ρ‚ΠΎΠ΄ вызываСтся, ΠΊΠΎΠ³Π΄Π° WebView устанавливаСт соСдинСниС с использованиСм ΡƒΡΡ‚Π°Ρ€Π΅Π²ΡˆΠ΅ΠΉ вСрсии TLS.

func webView(_ webView: WKWebView, authenticationChallenge challenge: URLAuthenticationChallenge, shouldAllowDeprecatedTLS decisionHandler: @escaping (Bool) -> Void)

decidePolicyFor
ΠœΠ΅Ρ‚ΠΎΠ΄ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ ΠΎ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΠΈ ΠΈΠ»ΠΈ ΠΎΡ‚ΠΊΠ»ΠΎΠ½Π΅Π½ΠΈΠΈ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ Π½Π° основС извСстного ΠΎΡ‚Π²Π΅Ρ‚Π°. Π—Π΄Π΅ΡΡŒ стоит ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ Ссли Π½Π΅ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ этот ΠΌΠ΅Ρ‚ΠΎΠ΄, Ρ‚ΠΎ WebView Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ запрос ΠΈ ΠΏΡ€ΠΈ нСобходимости ΠΏΠ΅Ρ€Π΅Π½Π°ΠΏΡ€Π°Π²ΠΈΡ‚ Π² Π΄Ρ€ΡƒΠ³ΠΎΠ΅ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅.

Web Navigation

Π”ΠΎΠ±Π°Π²ΠΈΠΌ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ основных Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ Π΄Π΅Π»Π΅Π³Π°Ρ‚Π° Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ ΠΈ ΠΏΡ€ΠΎΠΏΠΈΡˆΠ΅ΠΌ Π²Π΅Π·Π΄Π΅ Π²Ρ‹Π²ΠΎΠ΄ Π² консоль, для наглядности (ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŽ Π½ΡƒΠΆΠ½ΠΎ Π²ΠΈΠ΄Π΅Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ происходит ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅).

Π’ ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ makeUIView, Ρƒ WebView ΡƒΠΊΠ°ΠΆΠ΅ΠΌ ΠΊΠΎΠΎΡ€Π΄ΠΈΠ½Π°Ρ‚ΠΎΡ€, Π³Π΄Π΅ ΠΌΡ‹ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π»ΠΈ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» WKNavigationDelegate

webView.navigationDelegate = context.coordinator

ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ΠΌΡ‹ Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ decidePolicyFor, Π½ΡƒΠΆΠ½ΠΎ явно ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΏΠΎΠ»ΠΈΡ‚ΠΈΠΊΡƒ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ. WKNavigationActionPolicy – это пСрСчислСниС с двумя значСниями allow ΠΈ cancel. На Π΄Π°Π½Π½ΠΎΠΌ этапС ΠΌΡ‹ Ρ€Π°Π·Ρ€Π΅ΡˆΠΈΠΌ всС.

decisionHandler(.allow, preferences)

ΠŸΡ€ΠΎΡ‚Π΅ΡΡ‚ΠΈΡ€ΡƒΠΉΡ‚Π΅ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ ΠΈ посмотритС, ΠΊΠ°ΠΊ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅.

ЗаймСмся состояниСм Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π²Π΅Π±-страницы. Из описаний Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ становится понятно, ΠΊΠΎΠ³Π΄Π° Π½ΡƒΠΆΠ½ΠΎ ΡΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΈΠ»ΠΈ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ LoaderView – ΠΌΡ‹ просто сообщим ViewModel Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎΠ΅ состояниС.

self.parent.viewModel.isLoaderVisible.send(true)

Π’ ContentView ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π΅ΠΌ это дСйствиС. Π’Ρ‹Π·ΠΎΠ²Π΅ΠΌ Ρƒ VStack onReceive ΠΈ установим ΡΠΎΡΡ‚ΠΎΡΠ½ΠΈΡŽ isLoaderVisible Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΈΠ· ViewModel

VStack(spacing: 0) {
// views
}.onReceive(self.viewModel.isLoaderVisible.receive(on: RunLoop.main)) { value in self.isLoaderVisible = value }

ΠžΡ‚Π»ΠΈΡ‡Π½ΠΎ, процСсс Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π²Π΅Π±-страницы Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠ°ΠΊ часы.

ВСрнСмся Π² WebView ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π»ΠΎΠ³ΠΈΠΊΡƒ для дСйствий Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ Π² Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ didStartProvisionalNavigation

self.webViewNavigationSubscriber = self.parent.viewModel.webViewNavigationPublisher.receive(on: RunLoop.main).sink(receiveValue: { navigation in
				switch navigation {
					case .backward:
						if webView.canGoBack {
							webView.goBack()
						}
					case .forward:
						if webView.canGoForward {
							webView.goForward()
						}
					case .reload:
						webView.reload()
				}
			})

ΠžΡΡ‚Π°Π»ΠΎΡΡŒ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ дСйствий Π² WebNavigationView, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΌΡ‹ оставили пустыми. НСобходимо ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‚ΡŒ ViewModel Π²ΠΎ View ΠΈ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π΅ дСйствиС для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΊΠ½ΠΎΠΏΠΊΠΈ. НапримСр, для ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ:

viewModel.webViewNavigationPublisher.send(.reload)

ΠŸΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ Ρ‚Π΅ΡΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΡŽ, ΠΏΠΎΡ€Π°Π±ΠΎΡ‚Π°Π΅ΠΌ Π½Π°Π΄ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ΠΌ title Π²Π΅Π±-страницы.

ΠŸΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° evaluateJavaScript ΠΈΠ· webView ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ Π²Ρ‹Π·Π²Π°Ρ‚ΡŒ любой ΠΊΠΎΠ΄ Π½Π° JavaScript ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Π² Swift, Ρ‚.Π΅. ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π»ΡŽΠ±ΡƒΡŽ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ с Π²Π΅Π±-страницы. Π’ ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ didFinish, ΠΊΠΎΠ³Π΄Π° навигация Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π°, ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠΌ title ΠΈ сообщим Π΅Π³ΠΎ ViewModel.

 webView.evaluateJavaScript("document.title") { (response, error) in
                if let error = error {
		print("Error  evaluateJavaScript")
                    print(error.localizedDescription)
                }
                
                guard let title = response as? String else {
                    return
                }
                
                self.parent.viewModel.showWebTitle.send(title)
}

Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΏΠΎΠΊΠ°ΠΆΠ΅ΠΌ Π΅Π³ΠΎ Π² WebNavigationView.

Для этого Π΄ΠΎΠ±Π°Π²ΠΈΠΌ состояниС @State var webTitle = "", Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ Π±ΡƒΠ΄Π΅ΠΌ ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ Π² Text (размСстим Π΅Π³ΠΎ ΠΌΠ΅ΠΆΠ΄Ρƒ Divider ΠΈ Spacer).

Text(webTitle).onReceive(self.viewModel.webTitle.receive(on: RunLoop.main)) { value in
					self.webTitle = value
				}

Π“ΠΎΡ‚ΠΎΠ²ΠΎ! Π”Π°Π²Π°ΠΉΡ‚Π΅ протСстируСм, ΠΊΠ°ΠΊ это Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚.

ΠŸΠ΅Ρ€Π²Π°Ρ Ρ‡Π°ΡΡ‚ΡŒ Ρ†ΠΈΠΊΠ»Π° доступна ΠΏΠΎ ссылкС. ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ΅Π½ΠΈΠ΅ слСдуСт…

Π›Π£Π§Π¨Π˜Π• БВАВЬИ ПО Π’Π•ΠœΠ•

Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° программиста
16 фСвраля 2018

Мобильная Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π½Π° Python: ΠΎΠ±Π·ΠΎΡ€ Π΄Π²ΡƒΡ… Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠΎΠ²

Мобильная Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° Π½Π° Python – ΠΎΠ΄Π½ΠΎ ΠΈΠ· пСрспСктивных Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠΉ. Π’ ΡΡ‚Π°Ρ‚ΡŒ...
Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° программиста
13 фСвраля 2018

10 ΠΌΠΎΠ±ΠΈΠ»ΡŒΠ½Ρ‹Ρ… ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π°ΡƒΡ‡Π°Ρ‚ вас ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ

Π˜Ρ‰Π΅Ρ‚Π΅ курсы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π°ΡƒΡ‡Π°Ρ‚ вас ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ? ΠœΡ‹ собрали Π»ΡƒΡ‡ΡˆΠΈΠ΅ ΠΌΠΎΠ±ΠΈΠ»ΡŒΠ½Ρ‹...
Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° программиста
01 сСнтября 2017

15 классных ΠΈΠ΄Π΅ΠΉ для создания своСго мобильного прилоТСния

Если Π΄ΡƒΠΌΠ°Π»ΠΈ, Ρ‡Ρ‚ΠΎ Π½Π΅ΠΏΠ»ΠΎΡ…ΠΎ Π±Ρ‹ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ свои знания Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ Π½Π° ...