Реализуйте webkit с помощью swiftUI в macOS (и создайте предварительный просмотр веб-страницы)
Я пытаюсь создать приложение в строке меню, которое показывает папку "Входящие" этого сайта. Я хотел бы сделать простую функцию, которая открывает небольшое всплывающее окно с URL-адресом элемента (без открытия сафари). Элемент почтового ящика будет выглядеть примерно так
struct InboxItem: View {
@State var MesgSite: String = "https://duckduckgo.com"
@State private var showSafari = false
var body: some View {
VStack(alignment: .leading) {
Text("some text")
.background(SelectColor.opacity(0.5))
.onLongPressGesture {
//show preview of the MesgSite here
self.SelectColor = .blue
self.showSafari.toggle()
}.popover(isPresented: self.$showSafari) {
SafariPreview()
}
}
}
struct SafariPreview: View {
var body: some View {
VStack {
Text("Display the webpage here")
.padding()
}.frame(maxWidth: 533, maxHeight: 300)
}
}
Я хотел бы, чтобы при длительном нажатии на элемент он делал предварительный просмотр связанной веб-страницы, как в почтовом приложении по умолчанию на macOS, например:
Теперь всплывающее окно работает
Я пробовал добавить wkwebview
также как и SafariView
в NSViewRepresentable
, однако я получил (среди похожих) следующее сообщение об ошибке, используя код из этого сообщения SO
Use of undeclared type 'UIViewRepresentable'
TIA!
Редактировать:
Самую базовую версию проекта можно найти здесь, на github.
Изменить 2:
Уделял больше внимания вопросу
2 ответа
Я тоже пытался найти решение. Поскольку мне не удалось найти никакой документации на этом сайте, я дам решение, которое я нашел методом проб и ошибок.
Во-первых, как выясняется UI...
есть аналог в macOS, называемый NS...
. Таким образомUIViewRepresentable
было бы NSViewRepresentable
на macOS. Затем я нашел этот вопрос SO, в котором был примерWKWebview
на macOS. Объединив этот код с этим ответом на другой вопрос SO, я мог также обнаружить изменение URL-адреса, а также узнать, когда завершилась загрузка представления.
SwiftUI WebView в macOS
В результате получился следующий код. Для наглядности предлагаю поместить его в другой файл, напримерWebView.swift
:
Сначала импортируйте необходимые пакеты:
import SwiftUI
import WebKit
import Combine
Затем создайте модель, содержащую данные, к которым вы хотите иметь доступ в представлениях SwiftUI:
class WebViewModel: ObservableObject {
@Published var link: String
@Published var didFinishLoading: Bool = false
@Published var pageTitle: String
init (link: String) {
self.link = link
self.pageTitle = ""
}
}
Наконец, создайте struct
с участием NSViewRepresentable
это будет HostingViewController WebView()
вот так:
struct SwiftUIWebView: NSViewRepresentable {
public typealias NSViewType = WKWebView
@ObservedObject var viewModel: WebViewModel
private let webView: WKWebView = WKWebView()
public func makeNSView(context: NSViewRepresentableContext<SwiftUIWebView>) -> WKWebView {
webView.navigationDelegate = context.coordinator
webView.uiDelegate = context.coordinator as? WKUIDelegate
webView.load(URLRequest(url: URL(string: viewModel.link)!))
return webView
}
public func updateNSView(_ nsView: WKWebView, context: NSViewRepresentableContext<SwiftUIWebView>) { }
public func makeCoordinator() -> Coordinator {
return Coordinator(viewModel)
}
class Coordinator: NSObject, WKNavigationDelegate {
private var viewModel: WebViewModel
init(_ viewModel: WebViewModel) {
//Initialise the WebViewModel
self.viewModel = viewModel
}
public func webView(_: WKWebView, didFail: WKNavigation!, withError: Error) { }
public func webView(_: WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error) { }
//After the webpage is loaded, assign the data in WebViewModel class
public func webView(_ web: WKWebView, didFinish: WKNavigation!) {
self.viewModel.pageTitle = web.title!
self.viewModel.link = web.url?.absoluteString as! String
self.viewModel.didFinishLoading = true
}
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { }
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(.allow)
}
}
}
Этот код можно использовать следующим образом:
struct ContentView: View {
var body: some View {
//Pass the url to the SafariWebView struct.
SafariWebView(mesgURL: "https://stackru.com/")
}
}
struct SafariWebView: View {
@ObservedObject var model: WebViewModel
init(mesgURL: String) {
//Assign the url to the model and initialise the model
self.model = WebViewModel(link: mesgURL)
}
var body: some View {
//Create the WebView with the model
SwiftUIWebView(viewModel: model)
}
}
Создать предварительный просмотр Safari
Итак, теперь, когда у нас есть эти знания, относительно легко воссоздать удобный предварительный просмотр сафари.
Для этого обязательно добавьте @State private var showSafari = false
(который будет переключаться, когда вы хотите показать предварительный просмотр) в представление, которое вызовет предварительный просмотр.
Также добавьте .popover(isPresented: self.$showSafari) { ...
показать превью
struct ContentView: View {
@State private var showSafari = false
var body: some View {
VStack(alignment: .leading) {
Text("Press me to get a preview")
.padding()
}
.onLongPressGesture {
//Toggle to showSafari preview
self.showSafari.toggle()
}//if showSafari is true, create a popover
.popover(isPresented: self.$showSafari) {
//The view inside the popover is made of the SafariPreview
SafariPreview(mesgURL: "https://duckduckgo.com/")
}
}
}
Теперь SafariPreview struct
будет выглядеть так:
struct SafariPreview: View {
@ObservedObject var model: WebViewModel
init(mesgURL: String) {
self.model = WebViewModel(link: mesgURL)
}
var body: some View {
//Create a VStack that contains the buttons in a preview as well a the webpage itself
VStack {
HStack(alignment: .center) {
Spacer()
Spacer()
//The title of the webpage
Text(self.model.didFinishLoading ? self.model.pageTitle : "")
Spacer()
//The "Open with Safari" button on the top right side of the preview
Button(action: {
if let url = URL(string: self.model.link) {
NSWorkspace.shared.open(url)
}
}) {
Text("Open with Safari")
}
}
//The webpage itself
SwiftUIWebView(viewModel: model)
}.frame(width: 800, height: 450, alignment: .bottom)
.padding(5.0)
}
}
Результат выглядит так:
Ответ Chiel великолепен!
Но, возможно, кто-то ищет представление рендерера html без полных функций браузера
РАБОТАЕТ ДЛЯ MACOS
import SwiftUI
import WebKit
struct WebView: View {
@Binding var html: String
var body: some View {
WebViewWrapper(html: html)
}
}
struct WebViewWrapper: NSViewRepresentable {
let html: String
func makeNSView(context: Context) -> WKWebView {
return WKWebView()
}
func updateNSView(_ nsView: WKWebView, context: Context) {
nsView.loadHTMLString(html, baseURL: nil)
}
}
использование:
@State var text = "<html><body><h1>Hello World</h1><p><strong>hell</strong> yeah!</p></body></html>"
//......
TextField("", text: $text)
Divider()
WebView(html: $text)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)