基于Firebase平台开发(十一) —— Firebase D
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2021.05.27 星期四 |
前言
Firebase是一家实时后端数据库创业公司,它能帮助开发者很快的写出Web端和移动端的应用。自2014年10月Google收购Firebase以来,用户可以在更方便地使用Firebase的同时,结合Google的云服务。Firebase能让你的App从零到一。也就是说它可以帮助手机以及网页应用的开发者轻松构建App。通过Firebase背后负载的框架就可以简单地开发一个App,无需服务器以及基础设施。接下来几篇我们就一起看一下基于Firebase平台的开发。感兴趣的看下面几篇文章。
1. 基于Firebase平台开发(一) —— 基于ML Kit的iOS图片中文字的识别(一)
2. 基于Firebase平台开发(二) —— 基于ML Kit的iOS图片中文字的识别(二)
3. 基于Firebase平台开发(三) —— Firebase基本使用简介(一)
4. 基于Firebase平台开发(四) —— Firebase基本使用简介(二)
5. 基于Firebase平台开发(五) —— Firebase基本使用简介(三)
6. 基于Firebase平台开发(六) —— 基于Firebase Analytics的App使用率的跟踪(一)
7. 基于Firebase平台开发(七) —— iOS的A/B Test(一)
8. 基于Firebase平台开发(八) —— 使用Firebase Cloud Messaging进行Push Notification的发送和接收(一)
9. 基于Firebase平台开发(九) —— 使用Firebase Cloud Messaging进行Push Notification的发送和接收(二)
10. 基于Firebase平台开发(十) —— Firebase Dynamic Links的简单使用(一)
源码
1. Swift
首先看下工程组织结构
下面就是代码啦
1. AppMain.swift
import SwiftUI
import Firebase
@main
struct AppMain: App {
// Initialize Firebase
init() {
FirebaseApp.configure()
}
// Define deepLink
@State var deepLink: DeepLinkHandler.DeepLink?
var body: some Scene {
WindowGroup {
HomeView()
.accentColor(Color("rw-green"))
// Call onOpenURL
// 1
.onOpenURL { url in
print("Incoming URL parameter is: \(url)")
// 2
let linkHandled = DynamicLinks.dynamicLinks()
.handleUniversalLink(url) { dynamicLink, error in
guard error == nil else {
fatalError("Error handling the incoming dynamic link.")
}
// 3
if let dynamicLink = dynamicLink {
// Handle Dynamic Link
self.handleDynamicLink(dynamicLink)
}
}
// 4
if linkHandled {
print("Link Handled")
} else {
print("No Link Handled")
}
}
// Add environment modifier
.environment(\.deepLink, deepLink)
}
}
// MARK: - Functions
// Handle incoming dynamic link
func handleDynamicLink(_ dynamicLink: DynamicLink) {
guard let url = dynamicLink.url else { return }
print("Your incoming link parameter is \(url.absoluteString)")
// 1
guard
dynamicLink.matchType == .unique ||
dynamicLink.matchType == .default
else {
return
}
// 2
let deepLinkHandler = DeepLinkHandler()
guard let deepLink = deepLinkHandler.parseComponents(from: url) else {
return
}
self.deepLink = deepLink
print("Deep link: \(deepLink)")
// 3
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.deepLink = nil
}
}
}
2. HomeView.swift
import SwiftUI
struct HomeView: View {
var recipes: [Recipe] = Bundle.main.decode("recipe.json")
@State var cellSelected: Int?
// Define environment property
@Environment(\.deepLink) var deepLink
var body: some View {
NavigationView {
ScrollViewReader { proxy in
ScrollView(.vertical, showsIndicators: false) {
HStack {
Text("Raycipe")
.font(.largeTitle)
.bold()
Spacer()
}
.padding(.leading)
VStack(spacing: 20) {
ForEach(0..<recipes.count) { index in
NavigationLink(
destination: RecipeDetailView(recipe: recipes[index]), tag: index, selection: $cellSelected) {
RecipeCardView(recipe: recipes[index])
.padding(.horizontal)
.padding(.bottom)
.onTapGesture {
cellSelected = index
}
}
}
}
}
// Define navigation
// 1
.onChange(of: deepLink) { deepLink in
guard let deepLink = deepLink else { return }
switch deepLink {
case .details(let recipeID):
// 2
if let index = recipes.firstIndex(where: {
$0.recipeID == recipeID
}) {
// 3
proxy.scrollTo(index, anchor: .bottom)
// 4
cellSelected = index
}
case .home:
break
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
3. RecipeCardView.swift
import SwiftUI
struct RecipeCardView: View {
var recipe: Recipe
var body: some View {
VStack(alignment: .leading, spacing: 5) {
Image(recipe.image)
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: 220)
.clipped()
VStack(alignment: .leading, spacing: 12) {
Text(recipe.name)
.font(.title)
.fontWeight(.bold)
.foregroundColor(.black)
Text(recipe.description)
.font(.body)
.foregroundColor(Color.gray)
.italic()
.lineLimit(2)
RecipeInfoView(recipe: recipe)
}
.padding()
}
.background(Color.white)
.cornerRadius(12)
.shadow(color: Color.black.opacity(0.2), radius: 8, x: 0, y: 0)
}
}
struct RecipeCardView_Previews: PreviewProvider {
static var previews: some View {
RecipeCardView(recipe: Recipe.example)
}
}
4. RecipeDetailView.swift
import SwiftUI
import LinkPresentation
import Firebase
struct RecipeDetailView: View {
var recipe: Recipe
let screen = UIScreen.main.bounds
@State private var isShareSheetShowing = false
@State var activityIndicator = false
var body: some View {
ActivityIndicatorView(isShowing: $activityIndicator) {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .center) {
Image(recipe.image)
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: 260)
.clipped()
Group {
Text(recipe.description)
.lineLimit(nil)
.multilineTextAlignment(.leading)
.font(.body)
.padding(.top)
.frame(minHeight: 100)
RecipeInfoView(recipe: recipe)
Button(action: {
activityIndicator = true
// Call createDynamicLink
createDynamicLink()
}, label: {
Text("Share")
Image(systemName: "square.and.arrow.up")
})
.font(.title3)
.foregroundColor(Color("rw-green"))
Text("Ingredients")
.font(.title)
.bold()
.foregroundColor(Color.primary)
Text(recipe.ingredients)
.multilineTextAlignment(.leading)
Divider()
Text("Instructions")
.font(.title)
.bold()
.foregroundColor(Color.primary)
Text(recipe.instructions)
.lineLimit(nil)
.multilineTextAlignment(.leading)
}
.padding()
}
}
.navigationTitle(Text(recipe.name))
}
}
// MARK: - Functions
// Create dynamic link
func createDynamicLink() {
// TODO 1
var components = URLComponents()
components.scheme = "https"
components.host = "www.raywenderlich.com"
components.path = "/about"
// TODO 2
let itemIDQueryItem = URLQueryItem(name: "recipeID", value: recipe.recipeID)
components.queryItems = [itemIDQueryItem]
// TODO 3
guard let linkParameter = components.url else { return }
print("I am sharing \(linkParameter.absoluteString)")
// TODO 4
let domain = "https://rayciperw.page.link"
guard let linkBuilder = DynamicLinkComponents.init(link: linkParameter, domainURIPrefix: domain) else {
return
}
// TODO 5
// 1
if let myBundleId = Bundle.main.bundleIdentifier {
linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: myBundleId)
}
// 2
linkBuilder.iOSParameters?.appStoreID = "1481444772"
// 3
linkBuilder.socialMetaTagParameters = DynamicLinkSocialMetaTagParameters()
linkBuilder.socialMetaTagParameters?.title = "\(recipe.name) from Raycipe"
linkBuilder.socialMetaTagParameters?.descriptionText = recipe.description
// swiftlint:disable:next force_unwrapping
linkBuilder.socialMetaTagParameters?.imageURL = URL(string: """
https://pbs.twimg.com/profile_images/\
1381909139345969153/tkgxJB3i_400x400.jpg
""")!
// TODO 6
guard let longURL = linkBuilder.url else { return }
print("The long dynamic link is \(longURL.absoluteString)")
// TODO 7
linkBuilder.shorten { url, warnings, error in
if let error = error {
print("Oh no! Got an error! \(error)")
return
}
if let warnings = warnings {
for warning in warnings {
print("Warning: \(warning)")
}
}
guard let url = url else { return }
print("I have a short url to share! \(url.absoluteString)")
shareItem(with: url)
}
}
// Share dynamic link
func shareItem(with url: URL) {
activityIndicator = false
isShareSheetShowing.toggle()
let subjectLine = "Check out this tasty \(recipe.name) I found on Raycipe!"
let activityView = UIActivityViewController(activityItems: [subjectLine, url], applicationActivities: nil)
UIApplication.shared.windows.first?.rootViewController?.present(activityView, animated: true, completion: nil)
}
}
struct RecipeDetailView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
RecipeDetailView(recipe: Recipe.example)
}
}
}
5. RecipeInfoView.swift
import SwiftUI
struct RecipeInfoView: View {
// MARK: - PROPERTIES
var recipe: Recipe
var body: some View {
HStack(spacing: 20) {
Text(recipe.category.localizedUppercase)
.font(Font.callout.weight(.medium))
.foregroundColor(Color.white)
.frame(width: 120, height: 30)
.background(Color(recipe.category))
.cornerRadius(12)
Group {
HStack(alignment: .center, spacing: 5) {
Image(systemName: "person.2")
Text("\(recipe.numberOfServings)")
}
HStack(alignment: .center, spacing: 5) {
Image(systemName: "clock")
Text(recipe.preparationTime)
}
}
.foregroundColor(.gray)
}
}
}
struct RecipeCategoryView_Previews: PreviewProvider {
static var previews: some View {
RecipeInfoView(recipe: Recipe.example)
}
}
6. Recipe.swift
import Foundation
struct Recipe: Codable, Equatable {
let recipeID: String
let name: String
let image: String
let category: String
let preparationTime: String
let numberOfServings: Int
let description: String
let ingredients: String
let instructions: String
enum CodingKeys: String, CodingKey {
case recipeID = "recipe-id"
case name, image, category
case preparationTime = "preparation-time"
case numberOfServings = "number-of-servings"
case description, ingredients, instructions
}
#if DEBUG
static let example = Recipe(
recipeID: "003",
name: "Chocolate Cookies",
image: "cookies",
category: "Dessert",
preparationTime: "3.5 hrs",
numberOfServings: 16,
description: "Tasty!",
ingredients: "flour\nbutter\nchocolate\negg\nsugar",
instructions: "1. Whip everything\n\n2. Enjoy!")
#endif
}
7. DeepLinkHandler.swift
import Foundation
class DeepLinkHandler {
// DeepLink enum with two cases:
// home - for navigating to the home view
// details - for navigating to a specific recipe detailed view
// - uses the parsed recipeID query from url for navigation
enum DeepLink: Equatable {
case home
case details(recipeID: String)
}
// Parse url
func parseComponents(from url: URL) -> DeepLink? {
// 1
guard url.scheme == "https" else {
return nil
}
// 2
guard url.pathComponents.contains("about") else {
return .home
}
// 3
guard let query = url.query else {
return nil
}
// 4
let components = query.split(separator: ",").flatMap {
$0.split(separator: "=")
}
// 5
guard let idIndex = components.firstIndex(of: Substring("recipeID")) else {
return nil
}
// 6
guard idIndex + 1 < components.count else {
return nil
}
// 7
return .details(recipeID: String(components[idIndex + 1]))
}
}
8. DeepLinkKey.swift
import SwiftUI
struct DeepLinkKey: EnvironmentKey {
static var defaultValue: DeepLinkHandler.DeepLink? {
return nil
}
}
// MARK: - Define a new environment value property
extension EnvironmentValues {
var deepLink: DeepLinkHandler.DeepLink? {
get {
self[DeepLinkKey]
}
set {
self[DeepLinkKey] = newValue
}
}
}
9. ActivityIndicatorView.swift
import SwiftUI
struct ActivityIndicatorView<Content>: View where Content: View {
@Binding var isShowing: Bool
var content: () -> Content
var body: some View {
ZStack(alignment: .center) {
self.content()
.disabled(self.isShowing)
.blur(radius: self.isShowing ? 3 : 0)
VStack {
ProgressView("Sharing...")
.progressViewStyle(CircularProgressViewStyle())
}
.frame(width: 150, height: 150)
.background(Color.primary.colorInvert())
.foregroundColor(.primary)
.cornerRadius(20)
.shadow(color: .secondary, radius: 10, x: 1, y: 0)
.opacity(self.isShowing ? 1 : 0)
}
}
}
10. CodableBundleExtension.swift
import Foundation
// MARK: - JSON decode from bundle
extension Bundle {
func decode(_ file: String) -> [Recipe] {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
guard let loaded = try? decoder.decode([Recipe].self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}
后记
本篇主要讲述了
Firebase Dynamic Links
的简单使用,感兴趣的给个赞或者关注~~~