大师学SwiftUI第17章Part1 - Web内容访问及自定义Safari视图控制器

简介: App可以让用户访问网页,但实现的方式有不止一种。我们可以让用户通过链接在浏览器中打开文档、在应用界面中内嵌一个预定义的浏览器或是在后台下载并处理数据。

App可以让用户访问网页,但实现的方式有不止一种。我们可以让用户通过链接在浏览器中打开文档、在应用界面中内嵌一个预定义的浏览器或是在后台下载并处理数据。

链接

链接是一个关联表示文档位置的文本或图片。在用户点击链接时打开文档。链接设计之初用于网页,但我们可以将其插入应用,让系统决定在何处(浏览器或是其它应用)打开文档。SwiftUI自带有Link视图进行创建。

Link(String, destination: URL);初始化创建一个打开链接的按钮。第一个参数指定按钮的标题,destination参数是一个带有希望打开的文档位置的URL结构体。如果希望使用视图来展示标签,可以实现初始化方法Link(destination:,label")。

下例在点击按钮时打开alanhou.org。代码定义了一个@State属性存储URL,使用希望打开的链接进行初始化。我们使用该属性的值创建URL结构体并将其赋给Link视图。在点击按钮时,系统会读取URL,识别到它是一个网页链接,然后打开浏览器加载相应网站。

示例17-1:打开网站

struct ContentView: View {
    @State private var searchURL = "https://alanhou.org"
    var body: some View {
        NavigationStack {
            VStack {
                Link("Open Web", destination: URL(string: searchURL)!)
                    .buttonStyle(.borderedProminent)
                Spacer()
            }.padding()
        }
    }
}

图17-1:链接

✍️跟我一起做:创建一个多平台项目。使用示例17-1的代码更新ContentView视图。在iPhone模拟器上运行应用,点击按钮。系统会打开外部浏览器并加载网站。

本例中,我们在代码内定义了URL,但有时URL由用户提供或是通过另一个文档获取。这时,URL中可能包含不允许出现的字符,导致无法识别位置。要保障URL有效,我们需要将不安全的字符转化为百分号编码字符。这些字符由%接十六进制数字进行表示。为此String结构体中包含了如下方法。

  • addingPercentEncoding(withAllowedCharacters: CharacterSet):该方法返回一个字符串,参数指定的集合中所有字符都会使用百分号编码的字符进行替换。withAllowedCharacters参数是一个带类型属性的结构体,创建表示通用集合的实例。用于URL的有urlFragmentAllowedurlHostAllowedurlPasswordAllowedurlPathAllowedurlQueryAllowedurlUserAllowed

这一方法由NSString类实现,但可在String结构体的任意实例中使用。这意味着可以对希望检查的URL直接应用该方法,并将其赋值给Link视图。唯一的问题是这个视图要求URL已可处理,因此要先使用一个计算属性或方法检查其值。为简化这一处理,环境中包含一个名为openURL的属性,返回可用于打开URL的方法。下例实现了一个Button视图使用百分号编码字符替换掉无效字符,然后执行openURL()方法打开链接。

示例17-2:编码URL

struct ContentView: View {
    @Environment(\.openURL) var openURL
    @State private var searchURL = "https://alanhou.org"
    var body: some View {
        NavigationStack {
            VStack {
                Button("Open Web") {
                    if let url = searchURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
                        openURL(URL(string: url)!)
                    }
                }
                    .buttonStyle(.borderedProminent)
                Spacer()
            }.padding()
        }
    }
}

上例中,我们处理了一个知道没有问题的URL,但有时并不是这样。通常URL来自外部数据源或由用户提供。这时我们不仅要使用addingPercentEncoding()对值进行编码,还要确定存在所有的URL组件。 例如,用户只提供了域名(alanhou.org),没带协议(https),我们需要在尝试打开前创建完整的URL。为阅读、创建及修改URL组件,Foundation框架定义了URLComponents结构体。该结构体包含如下初始化方法。

  • URLComponents(string: String):这个初始化方法通过string参数指定的URL组成部分创建一个URLComponents结构体。

URLComponents结构体包含一些读取和修改组成部分的属性。下面是一些常用的。

  • scheme:这一属性设置或返回URL的协议(如http)。
  • host:该属性设置或返回URL的域名(如www.google.com)。
  • path:该属性设置或返回URL域名后的部分(/index.php)。
  • query:该属性设置或返回URL的参数(如id=22)。
  • queryItems:该属性设置或返回一个URLQueryItem结构体数组,包含URL中的所有参数。

URLComponents结构体还包含如下属性,返回一个由各组成部分创建URL的字符串。

  • string:该组成返回由各组成部分值构建URL的字符串。

在下例中,我们允许用户插入一个URL,但确保了一定会包含https协议。

示例17-3:编码自定义URL

struct ContentView: View {
    @Environment(\.openURL) var openURL
    @State private var searchURL = ""
    var body: some View {
        NavigationStack {
            VStack {
                TextField("Insert URL", text: $searchURL)
                    .textFieldStyle(.roundedBorder)
                    .autocapitalization(.none)
                    .autocorrectionDisabled(true)
                Button("Open Web") {
                    if !searchURL.isEmpty {
                        var components = URLComponents(string: searchURL)
                        components?.scheme = "https"
                        if let newURL = components?.string {
                            if let url = newURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
                                openURL(URL(string: url)!)
                            }
                        }
                    }
                }
                    .buttonStyle(.borderedProminent)
                Spacer()
            }.padding()
        }
    }
}

URLComponents结构体接收一个URL字符串,提取组成部分将它们赋值给结构体属性,以便读取或修改。本例中,我们将字符串httpsscheme属性保障URL有效,可由系统处理。组成部分就绪后,我们可通过string属性获取完成的URL,使用百分号编码的字符替换无效字符并打开。

图17-2:自定义URL

Safari视图控制器

链接为我们提供了在应用内对网页的访问,但是在外部应用中打开的文档。考虑到抓住用户的注意力非常重要,苹果内置了一个名为SafaraServices的框架。通过该框架,我们可以在应用中内置Safari流星器,为用户提供更好的体验。框架包含一个SFSafariViewController类,创建包含显示网页的视图及导航工具的视图控制器。

  • SFSafariViewController(url: URL, configuration: Configuration):这个初始化方法创建一个新的自动加载url参数指定网站的Safari视图控制器。configuration参数是SFSafariViewController类中Configuration类的对象的一个属性。可以使用的属性有entersReaderIfAvailablebarCollapsingEnabled

SFSafariViewController类创建一个UIKit视图控制器。因此,我们必须通过UIViewControllerRepresentable协议定义一个representable视图控制器,添加到我们的SwiftUI界面中,如下例所示。(更多有关representable视图控制器的内容,请参见第16章。)

示例17-4:创建Safari浏览器

import SwiftUI
import SafariServices
struct SafariBrowser: UIViewControllerRepresentable {
    @Binding var searchURL: URL
    func makeUIViewController(context: Context) -> SFSafariViewController {
        let safari = SFSafariViewController(url: searchURL)
        return safari
    }
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
    }
}

该结构体创建一个包含可使用的Safari游览器的视图控制器。下例中,我们在sheet弹窗中打开这个视图。

示例17-5:打开Safari游览器

struct ContentView: View {
    @State private var searchURL: URL = URL(string: "https://www.formasterminds.com")!
    @State private var openSheet: Bool = false
    var body: some View {
        VStack {
            Button("Open Browser") {
                openSheet = true
            }.buttonStyle(.borderedProminent)
            Spacer()
        }.padding()
            .sheet(isPresented: $openSheet) {
                SafariBrowser(searchURL: $searchURL)
            }
    }
}

这一视图定义了一个类型为URL@State属性,使用https://www.formasterminds.com进行初始化。点击按钮时,SafariBrowser视图使用该值进行初始化,在弹窗中打开浏览器并加载网站。

图17-3:Safari浏览器

✍️跟我一起做:创建一个多平台项目。使用示例17-4中的代码创建一个名为SafariBrowser.swift的Swift文件。使用示例17-5中的代码更新ContentView视图。在iPhone模拟器上运行程序,点击按钮。这时会在弹窗中打开Safari游览器访问网址https://www.formasterminds.com

SFSafariViewController类还提供了如下的配置属性:

  • dismissButtonStyle:该属性设置或返回一个值,用于决定视图控制器释放视图所显示的按钮类型。它是一个类型为DismissButtonStyle的枚举,值有done(默认值)、closecancel
  • preferredBarTintColor:该属性设置或返回一个决定导航栏颜色的UIColor值。
  • preferredControlTintColor:该属性设置或返回一个决定控件颜色的UIColor值。

下例使用这三个属性将浏览器的颜色适配www.formasterminds.com网站。

示例17-6:配置视图控制器

struct SafariBrowser: UIViewControllerRepresentable {
    @Binding var searchURL: URL
    func makeUIViewController(context: Context) -> SFSafariViewController {
        let safari = SFSafariViewController(url: searchURL)
        safari.dismissButtonStyle = .close
        safari.preferredBarTintColor = UIColor(red: 81/255, green: 91/255, blue: 119/255, alpha: 1.0)
        safari.preferredControlTintColor = UIColor.white
        return safari
    }
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
    }
}

示例17-6中的代码还修改了dismissButtonStyle属性,来改变浏览器所显示的按钮类型。Done按钮变成了Close

图17-4:自定义Safari视图控制器

注意UIColor类是由UIKit框架所定义的类。该类包含很多的初始化方法。最常的是UIColor(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)。这个类还包含一些创建预定义颜色的类型属性。当前可以使用的有systemBluesystemBrownsystemCyan、systemGreensystemIndigosystemMintsystemOrangesystemPinksystemPurplesystemRedsystemTealsystemYellowsystemGraysystemGray2systemGray3systemGray4systemGray5systemGray6clearblackbluebrowncyandarkGraygraygreenlightGraymagentaorangepurpleredwhiteyellow

在用户滚动页面时,控制器会收成导航栏为内容让出空间。这会对用户退出或访问工具造成困难。如果我们觉得应用保留导航栏为原始尺寸更为合理,可以使用Configuration对象初始化控制器。这个类位于SFSafariViewController类之中,包含如下控制导航栏的属性。

  • barCollapsingEnabled:这一属性设置或返回决定导航栏收起或展开的布尔值。

创建好Configuration对象后,我们可以配置这个属性,通过控制器的初始化方法将其赋值给Safari视图控制器。

示例17-7:导航栏保留为原始大小

struct SafariBrowser: UIViewControllerRepresentable {
    @Binding var searchURL: URL
    func makeUIViewController(context: Context) -> SFSafariViewController {
        let config = SFSafariViewController.Configuration()
        config.barCollapsingEnabled = false
        let safari = SFSafariViewController(url: searchURL, configuration: config)
        return safari
    }
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
    }
}

✍️跟我一起做:使用示例17-7中的代码更新SafariBrowser结构体。运行应用、滑动页面。导航栏会保持在原始大小,按钮也一直可见。

该框架还定义了一个SFSafariViewControllerDelegate协议,这样可以对Safari视图控制器添加一个代理用于控制流程。以下是一部分协议中定义的方法。

  • safariViewController(SFSafariViewController, didCompleteInitialLoad: Bool):这个方法在初始网站完成加载时由控制器调用。
  • safariViewControllerDidFinish(SFSafariViewController):这一方法在视图释放后(用户点击Done按钮)由控制器调用。

Safari视图控制器有一个delegate属性用于设置代理。下例中创建了一个coordinator,赋值给了视图的代理,并实现了safariViewControllerDidFinish()方法来在用户释放视图时禁用界面上的按钮。(用户仅能打开视图一次。)

示例17-8:为Safari视图控制器添加代理

struct SafariBrowser: UIViewControllerRepresentable {
    @Binding var disable: Bool
    @Binding var searchURL: URL
    func makeUIViewController(context: Context) -> SFSafariViewController {
        let config = SFSafariViewController.Configuration()
        config.barCollapsingEnabled = false
        let safari = SFSafariViewController(url: searchURL, configuration: config)
        safari.delegate = context.coordinator
        return safari
    }
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {
    }
    func makeCoordinator() -> SafariCoordinator {
        SafariCoordinator(disableCoordinator: $disable)
    }
}
class SafariCoordinator: NSObject, SFSafariViewControllerDelegate {
    @Binding var disableCoordinator: Bool
    init(disableCoordinator: Binding<Bool>) {
        self._disableCoordinator = disableCoordinator
    }
    func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
        disableCoordinator = true
    }
}

在这个视图中,我们需要定义一个@State属性存储一个布尔值并在Button视图中实现disable()修饰符来根据这个值启用或禁用按钮。

示例17-9:通过Safari视图控制器代理禁用按钮

struct ContentView: View {
    @State private var searchURL: URL = URL(string: "https://www.formasterminds.com")!
    @State private var openSheet: Bool = false
    @State private var disableButton: Bool = false
    var body: some View {
        VStack {
            Button("Open Browser") {
                openSheet = true
            }.buttonStyle(.borderedProminent)
                .disabled(disableButton)
            Spacer()
        }.padding()
            .sheet(isPresented: $openSheet) {
                SafariBrowser(disable: $disableButton, searchURL: $searchURL)
            }
    }
}

本例中,我们添加了一个Bool类型的@State属性disableButton,将其传递给representable视图控制器,因此可以通过coordinator修改其值。在释放Safari视图控制器时,执行safariViewControllerDidFinish()方法,disableButton属性的设为true,因此用户无法再次点击按钮。

✍️跟我一起做:使用示例17-8中的代码更新SafariBrowser.swift文件、示例17-9中的代码更新ContentView视图。在iPhone模拟中运行应用、按下按钮。点击Done按钮关闭Safari视图控制器。此时按钮被禁用。


其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记

相关文章
|
3月前
|
安全 前端开发 API
【Azure 应用服务】Azure Web App 服务默认支持一些 Weak TLS Ciphers Suite,是否有办法自定义修改呢?
【Azure 应用服务】Azure Web App 服务默认支持一些 Weak TLS Ciphers Suite,是否有办法自定义修改呢?
|
17天前
【Azure App Service】PowerShell脚本批量添加IP地址到Web App允许访问IP列表中
Web App取消公网访问后,只允许特定IP能访问Web App。需要写一下段PowerShell脚本,批量添加IP到Web App的允许访问IP列表里!
|
20天前
|
前端开发 开发者
WEB自定义页面请求响应
Web组件支持在应用拦截到页面请求后自定义响应请求能力。开发者通过onInterceptRequest()接口来实现自定义资源请求响应 。自定义请求能力可以用于开发者自定义Web页面响应、自定义文件资源响应等场景。
22 0
|
2月前
|
数据处理 Python
Django视图:构建动态Web页面的核心技术
Django视图:构建动态Web页面的核心技术
|
3月前
|
Java 应用服务中间件 Apache
使用IDEA修改Web项目访问路径,以及解决Apache Tomcat控制台中文乱码问题
本文介绍了在IntelliJ IDEA中修改Web项目访问路径的步骤,包括修改项目、模块、Artifacts的配置,编辑Tomcat服务器设置,以及解决Apache Tomcat控制台中文乱码问题的方法。
212 0
使用IDEA修改Web项目访问路径,以及解决Apache Tomcat控制台中文乱码问题
|
3月前
|
运维 安全 网络安全
"革新远程访问体验:Docker化部署webssh2,一键启动Web SSH客户端,让远程管理如虎添翼!"
【8月更文挑战第2天】Docker作为软件开发与运维的关键工具,以其轻量级、可移植及强隔离特性简化了应用部署。结合webssh2这一开源Web SSH客户端,可通过浏览器安全便捷地访问SSH服务器,无需额外软件。首先确保已安装Docker,接着拉取webssh2镜像并运行容器,映射端口以便外部访问。配置好SSH服务器后,通过浏览器访问指定URL即可开始SSH会话。此方案不仅提升了用户体验,还加强了访问控制与系统安全。
319 7
|
3月前
|
Java 数据库连接 数据库
强强联手!JSF 与 Hibernate 打造高效数据访问层,让你的应用如虎添翼,性能飙升!
【8月更文挑战第31天】本文通过具体示例详细介绍了如何在 JavaServer Faces (JSF) 应用程序中集成 Hibernate,实现数据访问层的最佳实践。首先,创建一个 JSF 项目并在 Eclipse 中配置支持 JSF 的服务器版本。接着,添加 JSF 和 Hibernate 依赖,并配置数据库连接池和 Hibernate 配置文件。然后,定义实体类 `User` 和 DAO 类 `UserDAO` 处理数据库操作。
60 0
|
3月前
|
API UED 开发者
Vaadin路由魔法:导航之舟,带你穿越页面迷宫!驾驭神奇URL,解锁无限可能!
【8月更文挑战第31天】Vaadin是一款现代Java Web开发框架,其路由机制结合前后端路由,确保流畅的用户体验和高效服务器资源利用。通过`@Route`注解和`Router`类,开发者可以轻松定义和管理页面路径。例如,`@Route(&quot;home&quot;)`可指定视图路径,而参数化路由如`@Route(&quot;user/:userId&quot;)`则允许URL传参。此外,Vaadin还提供了丰富的导航API和自定义路由事件监听器,助力开发者构建结构清晰且体验优秀的Web应用。
45 0
|
3月前
|
前端开发 API 数据处理
构建高效现代Web应用:深入探讨Entity Framework Core与GraphQL在数据访问中的结合使用
【8月更文挑战第31天】随着Web应用的发展,传统的RESTful API逐渐显现出局限性,现代应用开始转向GraphQL。与此同时,Entity Framework Core(EF Core)作为强大的ORM工具,在数据访问方面表现出色,支持异步操作和自动变更跟踪,简化了数据处理。GraphQL作为一种灵活的查询语言,允许客户端精确获取所需数据,减少不必要的传输。将EF Core与GraphQL结合使用,可实现高效的数据访问和灵活的数据查询,优化数据流并提升应用性能。这种技术组合不仅提高了开发效率,还优化了用户体验,有望成为未来Web开发的重要方向。
29 0
|
3月前
|
缓存 前端开发 JavaScript
Angular邂逅PWA:一场关于如何利用现代Web技术栈中的明星框架与渐进式理念,共同编织出具备原生应用般丝滑体验、离线访问及桌面集成能力的未来Web应用的探索之旅
【8月更文挑战第31天】本文详细介绍如何利用Angular将传统Web应用升级为渐进式Web应用(PWA),克服后者在网络依赖、设备集成及通知功能上的局限。通过具体命令行操作与代码示例,指导读者从新建Angular项目到配置`manifest.json`和服务工作进程,最终实现离线访问、主屏添加及推送通知等功能,显著提升用户体验。适合各水平开发者学习实践。
38 0