ios 路由

项目中使用路由方式

1.0 深度链接 2.0 推送 3.0 站内信 4.0 web跳转

1. 深度链接

简介

深度链接是指当用户打开移动应用时向其提供个性化的内容,或将用户带到应用内特定位置的操作。通过这种操作,您可以为用户提供优质的用户体验,从而极大加强用户与应用的互动。

两种深度链接类型

深度链接(Deep Linking):向已经安装了移动应用的现有用户直接展示个性化内容。
延迟深度链接(Deferred Deep Linking):当新用户或已卸载的用户完成安装打开移动应用时向其展示个性化内容。

深度链接方法

a. URI Scheme

iOS 8 及更低版本或者iOS 9 及更高版本(当使用的浏览器或移动应用不支持通用链接Universal Links时)
在app中的plist 文件添加URL条目(scheme://)

b. 通用链接Universal Links

在 iOS 9 及更高版本中,Apple用通用链接替代 URI Scheme作为主要的深度链接方法。 这种方法的安全性更高,因为其他应用无法获取通用链接。 用户体验也更好,因为可以直接启动iOS应用,不会将用户先重定向至浏览器。
本质上,通用链接的方法是将iOS移动应用和相关的网站/域连接,例如 AppsFlyer 的 OneLink 域 (xxx.onelink.me)
具体实现通过托管 ‘apple-app-site-association’ 文件(本来是需要上传到我们的web服务器)

深度链接流程

a. 用户单击 OneLink 链接。
如果用户安装了应用程序,通用链接或 URI 方案会打开应用程序。
如果用户没有安装应用程序,他们将被重定向到应用程序商店,下载后,用户打开应用程序。
b. 应用打开会触发SDK
c. SDK会运行UDL API
d. UDL API会从对应的服务器检索相应的数据
e. 通过对应的代理或回调返回给app相应的数据
f. app端拿到数据进行相应的处理

项目中使用深度链接的SDK

a. AppsFlyer的Onelink
b. Facebook
c. Google的Firebase

Facebook和Firebase事件处理
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    /// 接收URL,根据URL做相应的处理
}
Onelink事件处理(DeepLinkDelegate代理处理深度连接)
func didResolveDeepLink(_ result: DeepLinkResult) {
    if( result.deepLink?.isDeferred ) {
        print("This is a deferred deep link")
        } else {
        print("This is a direct deep link")
    }
}

2. 推送(本地推送和远程推送)

a. 本地推送

是由本地应用触发的。一般是基于时间的一种通知形式,如闹钟、待办事件等的提醒。
可以理解为”不联网”;即使没有网络情况下,也可以推送通知消息。

流程示例

1.0 注册

let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
    if let error = error {
        // Handle the error here.
    }
    // Enable or disable features based on the authorization.
}
center.delegate = self

2.0 实现一个简单的本地推送

func sendLocalPush() {
    let content = UNMutableNotificationContent()
    content.title = "Weekly Staff Meeting"
    content.body = "Every Tuesday at 2pm"
    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: (10), repeats: false)
    // Create the request
    let uuidString = UUID().uuidString
    let request = UNNotificationRequest(identifier: uuidString,
                content: content, trigger: trigger)
    // Schedule the request with the system.
    let notificationCenter = UNUserNotificationCenter.current()
    notificationCenter.add(request) { (error) in
       if error != nil {
          // Handle any errors.
       }
    }
}

3.0 代理实现(对推送消息进行处理)

@available(iOS 10, *)
extension AppDelegate: UNUserNotificationCenterDelegate {
    /// 应用程序在前台,当一个通知到达时,会触发此方法,可以在此做一些需要处理的事件
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        /// 设置以何种展示形式通知用户
        completionHandler([.banner, .list])
    }
    /// 用户点击处理通知时会触发此事件,在此可以对已经定义好的事件做相应的处理
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        print(response)
        /// 调用completionHandler块让系统知道您已经完成了对用户响应的处理,如果不实现,应用程序将永远不会响应。
        completionHandler()
    }
}

a. 远程推送(APNs:Apple Push Notification Services)

APNs推送流程:

1.0 应用需要向APNs服务器注册,注册成功后APNs服务器会返回一个deviceToken,并且二者之间会维持一个长连接(这个长连接是基于SSL协议的TCP流通讯)

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
          /// 注册远程推送
          application.registerForRemoteNotifications()
    }

2.0 拿到这个deviceToken后我们将这个deviceToken发给我们自己的服务器。(如果使用三方推送就发送给三方服务器)

    /// 接收设备的deviceToken
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        /// 将deviceToken发送给服务器
        print("APNs token retrieved: \(deviceToken)")
    }

3.0 当有消息需要被推送时,服务器会将消息按指定的格式结合设备的deviceToken一并打包,然后发给APNs服务器。
4.0 APNs将新消息推送给我们的设备上,然后就在设备的屏幕上显示出来了 (因为我们的设备和APNs服务器二者之间维持了一个长连接)

    /// 应用程序在前台或后台运行时调用这个方法,如果你启用了远程通知后台模式,当远程通知到达时,系统会启动你的应用程序(或从挂起状态唤醒它)并将其置于后台状态。如果用户强制退出应用程序,系统不会自动启动应用程序。
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
          /// 在此处可以根据产品需求来自定义实现方式
         /// 一旦你完成了通知的处理,你必须在handler参数中调用block,否则你的应用将被终止。
        completionHandler(UIBackgroundFetchResult.newData)
    }
@available(iOS 10, *)
extension AppDelegate: UNUserNotificationCenterDelegate {
    /// 应用程序在前台,当一个通知到达时,会触发此方法,可以在此做一些需要处理的事件
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        /// 设置以何种展示形式通知用户
        completionHandler([.banner, .list])
    }
    /// 用户点击处理通知时会触发此事件,在此可以对已经定义好的事件做相应的处理
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        print(response)
        /// 调用completionHandler块让系统知道您已经完成了对用户响应的处理,如果不实现,应用程序将永远不会响应。
        completionHandler()
    }
}

3. 站内信

遇到的问题: 链接中包含空格或者中文
处理: 进行百分比编码处理

4. web跳转

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    /// js交互回调,根据我们自定义事件,进行相应的处理
}

数据处理流程

数据初处理

    /// 打开链接
    /// 此方法就是校验传入的数据是否是一个URL,是的话就继续往下操作
    public func openTheLink(_ data: Any?) {        
        guard let dataNew = data else { return }
        if dataNew is URL {
            useUrlLinksToJump(dataNew as! URL)
        }
        if dataNew is String {
            guard let url = URL(string: dataNew as! String) else {
                let str = dataNew as! String
                /// 遇到过的问题
                /// 对其字符串中的非法字符(比如空格等)进行百分比编码处理,否则将识别不出来这是个url,导致无法继续操作
                if let url = URL(string: str.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "") {
                    useUrlLinksToJump(url)
                }
                return
            }
            useUrlLinksToJump(url)
        }
    }

    /// 打开通知及Firebase推送跳转(字典类型数据)
    /// 此方法是对本地通知及Firebase推送回调的字典数据进行处理,具体处理及描述如代码所示
    public func openTheNotification(userInfo: [AnyHashable: Any]) {
        let webUrl = (userInfo["url"] as? String ?? "").removingPercentEncoding
        let bookId: String = userInfo["book_id"] as? String ?? ""
        let tab: String = userInfo["tab"] as? String ?? ""
        var deepLink = ""
        if let deeplink = userInfo["deeplink"] as? String {
            deepLink = deeplink
            /// 先判断是不是一个H5链接,是的话优先处理跳转
            /// 遇到过的问题
            /// 对其字符串中的非法字符(比如空格等)进行百分比编码处理,否则将识别不出来这是个url,导致无法继续操作
            if let url = URL(string: deeplink.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "") {
                if url.scheme == "https" || url.scheme == "http" {
                    useUrlLinksToJump(url)
                    return
                }
            }
        }
        let aps = userInfo["aps"] as? NSDictionary
        /// 此处category是一个定义好的跳转标识,有的话就会进行一个特定链接的跳转
        if let category = aps?["category"] as? String {
             /// 遇到过的问题
            /// 此处对url字符串要进行去除百分比编码,否则数据获取的将不准确
            let link = deepLink.removingPercentEncoding
            /// 特定链接跳转
            specificLinkJump(key: category, book_id: bookId, url: webUrl, tab: tab, deeplink: link)
        }
    }

URL的处理

    /// 判断URL是自定义链接还是H5
    func useUrlLinksToJump(_ url: URL) {
        if url.scheme == "https" || url.scheme == "http" {
            var urlStr = url.absoluteString
            /// 判断H5是跳站内还是站外
            if url.host == "h5" || url.host == "h5cdn" || url.absoluteString.hasPrefix(XMSDKManager.shared.baseHttpUrl) {
                urlStr = urlStr.replacingOccurrences(of: "\(url.scheme!)://\(url.host!)/", with: baseHttpUrl)
                if XMWebOfflineResourcesManager.shared.resourcesAvailable(resources: url.lastPathComponent) {
                    urlStr.replaceSubrange(..<urlStr.firstIndex(of: ":")!, with: targetName.lowercased())
                }
                delegate?.routeActionWithType(type: XMRouteEventsTypeEvent(eventType: .web(url: urlStr, isActive: false)))
            } else {
                delegate?.routeActionWithType(type: XMRouteEventsTypeEvent(eventType: .openTheOutboundLink(url: url)))
            }
        } else {
            /// 自定义连接
            pageLinkJumpWithUrl(url: url)
        }
    }
    /// 编辑url->拿到我们想要的数据
    func editorURL(url: URL) -> (componentsUrlArr: [String], componentsItemsDic: [String: String]) {
        var componentsUrlArr = [String]()
        var dic = [String: String]()
        if let components = URLComponents(url: url, resolvingAgainstBaseURL: true) {
            if let componentsUrl = components.url {
                componentsUrlArr = componentsUrl.path.components(separatedBy: "/")
                componentsUrlArr = componentsUrlArr.filter({ $0 != "" && $0 != " " })
            }
            for item in components.queryItems ?? [URLQueryItem]() {
                if let value = item.value {
                    dic.updateValue(value, forKey: item.name)
                }
            }
        }
        return (componentsUrlArr, dic)
    }

特定链接跳转

    /// 特定链接跳转
    public func specificLinkJump(key: String, book_id: String? = nil, url: String? = nil, tab: String? = nil, deeplink: String? = nil, _ webvc: UIViewController? = nil) {
        switch key {
        /// 指定tabbar的跳转
        case XMRouteKeyNameType.homePage.rawValue:
            var tabBarType: XMRouteTabBarNameType = .store
            if tab == XMRouteTabBarNameType.bookshelf.rawValue {
                tabBarType = .bookshelf
                XMBuryPointManager.shared.platformsEventUpload(event: XMTripartitelogCustomEventType.click_library.rawValue)
            } else if tab == XMRouteTabBarNameType.store.rawValue {
                tabBarType = .store
                XMBuryPointManager.shared.platformsEventUpload(event: XMTripartitelogCustomEventType.click_featured.rawValue)
            } else if tab == XMRouteTabBarNameType.userCenter.rawValue {
                tabBarType = .userCenter
                XMBuryPointManager.shared.platformsEventUpload(event: XMTripartitelogCustomEventType.click_mine.rawValue)
            }
            /// 先实现tabbar的切换,如果deeplink有值,再实现deeplink的跳转。
            let jump = {
                if let link = deeplink, !link.isEmpty,
                   let url = URL(string: link.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "") {
                    self.pageLinkJumpWithUrl(url: url)
                }
            }
            if let delegate = delegate {
                /// 遇到过的问题
                /// 一定要在实现代理的地方调用jump闭包,否则deeplink如果有值的话,将不会实现
                delegate.routeActionWithType(type: XMRouteEventsTypeEvent(eventType: .selectTabBar(tabBarType: tabBarType, completion: jump)))
            } else {
                jump()
            }
            return
        default:
            break
        }
        /// 通过指定的key进行跳转
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
            switch (key) {
            case XMRouteKeyNameType.novelDetailPage.rawValue, XMRouteKeyNameType.bookDetailPage.rawValue: // 书籍详情
                if book_id != "" || book_id != nil {
                    /// 在实现代理的地方实现逻辑处理
                    self.delegate?.routeActionWithType(type: XMRouteEventsTypeEvent(eventType: .bookInfo(bid: book_id)))
                }
                break
            default:
                break
            }
        }
    }

页面链接跳转

    func pageLinkJumpWithUrl(url: URL) {
        
        if !schemes.contains(where: { $0.caseInsensitiveCompare(url.scheme ?? "") == .orderedSame }) { return }
        let results = editorURL(url: url)
        let componentsUrlArr = results.componentsUrlArr
        let componentsItemsDic = results.componentsItemsDic
        switch componentsUrlArr.first {
            /// 通过判断我们自定义好的事件,实现代码回调
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 原文链接:https://github.com/halfrost/Halfrost-Field/blob/mast...
    hament阅读 5,727评论 1 31
  • 前端的路由 网络中路由概念是指路由器从一个接口上接收到数据包,根据数据包的目的地址进行定向转发到另一个接口的过程。...
    小白进城阅读 897评论 0 5
  • 用到的组件 1、通过CocoaPods安装 2、第三方类库安装 3、第三方服务 友盟社会化分享组件 友盟用户反馈 ...
    SunnyLeong阅读 14,736评论 1 180
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,122评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,934评论 0 2