使用Firebase的iOS应用开发面向初学者

使用Pring开发iOS应用

本文是针对尚未在Firebase上进行开发的开发人员的。 我将尽可能详细地解释这项工作,以便Android开发人员和Web开发人员可以立即开始。

https://github.com/1amageek/Pring

开发环境

  • macOS High Sierra
  • Xcode 9.4
  • 斯威夫特4
  • iOS 11

创建一个Xcode项目并准备Firebase

让我们创建一个新的Xcode项目

首先,启动Xcode。 让我们做一个新项目。

在这里,选择Master-Detail App。

请在产品名称中输入项目的名称。 我命名为Firebase Sample。
这是非常重要的,因为捆绑包标识符是从组织标识符生成的。 如果您有域,请使用它。

请关闭一次Xcode项目。

使用Cocoapods安装Firebase SDK

请使用Terminal移至项目目录。
在项目目录中执行以下命令。

 荚初始化 

如果生成Podfile ,则表示成功。

如果遇到错误,请安装Cocoapods。

https://cocoapods.org/

编辑您的Podfile

 平台:ios,“ 11.0” 
 目标“ Firebase示例” 
use_frameworks!
 吊舱“ Pring” 
 结束 

保存您的Podfile并执行以下命令

 吊舱安装 

如果显示如下,则表示成功。

更多文件将添加到项目目录。
创建一个名为Firebase Sample.xcworkspace的文件。 打开这个

请确认Pods已添加到项目中,如下所示。

Firebase SDK安装到此结束。

准备Firebase

我安装了Firebase SDK,但这并不意味着我可以使用Firebase。 访问Firebase控制台并配置数据库。

https://console.firebase.google.com/?pli=1

让我们转到Firebase并添加一个新项目。

从这里开始,已经提出了很棒的文章,所以请在这里参考。

迅捷で始めるFirebase入门

完成这项工作后,将像这样添加GoogleService-Info.plist

让我们开始开发

在使用Xcode创建的项目的初始设置中,使用Main.storyboard启动ViewController,但这一次,使用AppDelegate启动ViewController的应用程序。

首先,删除项目的“部署信息”的主界面

接下来,如下编辑AppDelegate

 导入UIKit 
导入Firebase
  @UIApplicationMain 
AppDelegate类:UIResponder,UIApplicationDelegate,UISplitViewControllerDelegate {
  var window:UIWindow? 

  func application(_ application:UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey:Any]?)->布尔{ 
  FirebaseApp.configure() 
 让故事板:UIStoryboard = UIStoryboard(名称:“ Main”,包:nil) 
让splitViewController:UISplitViewController = storyboard.instantiateInitialViewController()为! UISplitViewController
让navigationController = splitViewController.viewControllers [splitViewController.viewControllers.count-1]为! UINavigationController
navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
splitViewController.delegate =自我
  self.window = UIWindow(框架:UIScreen.main.bounds) 
self.window?.rootViewController = splitViewController
self.window?.makeKeyAndVisible()
 返回真 
}

  // MARK:-拆分视图 
  func splitViewController(_ splitViewController:UISplitViewController,collapseSecondary secondaryViewController:UIViewController,移至primaryViewController:UIViewController)-> Bool { 
守卫让secondaryAsNavController = secondaryViewController为? UINavigationController else {返回false}
警卫让topAsDetailController = secondaryAsNavController.topViewController为? DetailViewController else {返回false}
如果topAsDetailController.detailItem == nil {
//返回true表示我们已经采取了任何措施来处理崩溃; 辅助控制器将被丢弃。
返回真
}
返回假
}
  } 

让我们在这里构建一次。

如果显示纯白色MasterViewController,将成功。

注意
您这次没有从Xcode启动Main.storyboard的原因是在Firebase初始化时。

application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool写入FirebaseApp.configure() application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
由于Main.storyboard在此之前已加载,因此Firebase不能与MainViewController一起使用。

保存到Firebase

首先,让我们编写一个保存数据的模型。
接下来,将一个新的Swift文件添加到项目中。

这次我们创建一个名为Item的模型。

像这样编辑项目
Pring是一个可以将Cloud Firestore文档作为对象处理的库,您可以按以下方式编写Item。

 进口品 
  @objcMembers 
类项目:对象{
 动态var名称:字符串? 
  } 

让我们将Item保存到Firebase。
如下编辑MasterViewController func insertNewObject(_ sender: Any)

  @objc 
func insertNewObject(_ sender:Any){
让项目:项目= Item()
item.name =“巨大”
item.save()
}

用Xcode构建它。

让我们点击右上角的“ +”按钮。

如果您访问Firebase控制台并按如下所示出现在CloudFirestore中,则表示成功。

如果不是,请检查安全规则。 现在,我们正在应用一个非常宽松的安全规则来执行示例代码,但是请注意,因为它不能作为产品使用。

连接MasterView Controller和Firebase

物品可以保存在Firebase中。 接下来,让我们在MasterViewController的TableView中显示项目。

首先,将Pring导入MasterViewController

 导入UIKit 
进口品

接下来,我们将准备数据源

  var dataSource:DataSource ? 

请在viewDidLoad添加以下viewDidLoad 。 在这里,我不会对其进行深入的解释,但这将是相关的。

 覆盖func viewDidLoad(){ 
super.viewDidLoad()
//加载视图后进行其他任何设置,通常是从笔尖进行。
navigationItem.leftBarButtonItem = editButtonItem
 让addButton = UIBarButtonItem(barButtonSystemItem:.add,target:self,action:#selector(insertNewObject(_ :))) 
navigationItem.rightBarButtonItem = addButton
如果让split = splitViewController {
让控制器= split.viewControllers
detailViewController =(controllers [controllers.count-1] as!UINavigationController).topViewController as? DetailViewController
}
  // 数据源 
self.dataSource = Item.query.dataSource()
.on({[弱自我](快照,更改)在
守护让tableView:UITableView = self?.tableView else {return}
切换更改{
大小写.initial:
tableView.reloadData()
case .update(允许删除,让插入,让修改):
tableView.beginUpdates()
tableView.insertRows(at:inserts.map {IndexPath(row:$ 0,section:0)},其中:.automatic)
tableView.deleteRows(at:deletes.map {IndexPath(row:$ 0,section:0)},其中:.automatic)
tableView.reloadRows(at:changes.map {IndexPath(row:$ 0,section:0)},其中:.automatic)
tableView.endUpdates()
大小写.error(让错误):
打印(错误)
}
})。听()
  } 

通过重写TableView的委托,完成工作,如下所示。

 覆盖func tableView(_ tableView:UITableView,numberOfRowsInSection部分:Int)-> Int { 
返回self.dataSource?.count? 0
}
 覆盖func tableView(_ tableView:UITableView,cellForRowAt indexPath:IndexPath)-> UITableViewCell { 
let cell = tableView.dequeueReusableCell(withIdentifier:“ Cell”,for:indexPath)
让项目:项目= self.dataSource![indexPath.row]
cell.textLabel!.text = item.name
返回单元
}
 覆盖func tableView(_ tableView:UITableView,提交editingStyle:UITableViewCellEditingStyle,forRowAt indexPath:IndexPath){ 
如果editingStyle == .delete {
self.dataSource!.removeDocument(at:indexPath.row)
}否则,如果editingStyle == .insert {
//创建适当类的新实例,将其插入数组,然后在表视图中添加新行。
}
}

加载MasterView Controller后,它如下所示。

 导入UIKit 
进口品
 类MasterViewController:UITableViewController { 
  var detailViewController:DetailViewController?  =无 
  var dataSource:DataSource ? 
 覆盖func viewDidLoad(){ 
super.viewDidLoad()
//加载视图后进行其他任何设置,通常是从笔尖进行。
navigationItem.leftBarButtonItem = editButtonItem
 让addButton = UIBarButtonItem(barButtonSystemItem:.add,target:self,action:#selector(insertNewObject(_ :))) 
navigationItem.rightBarButtonItem = addButton
如果让split = splitViewController {
让控制器= split.viewControllers
detailViewController =(controllers [controllers.count-1] as!UINavigationController).topViewController as? DetailViewController
}
  self.dataSource = Item.query.dataSource() 
.on({[弱自我](快照,更改)在
守护让tableView:UITableView = self?.tableView else {return}
切换更改{
大小写.initial:
tableView.reloadData()
case .update(允许删除,让插入,让修改):
tableView.beginUpdates()
tableView.insertRows(at:inserts.map {IndexPath(row:$ 0,section:0)},其中:.automatic)
tableView.deleteRows(at:deletes.map {IndexPath(row:$ 0,section:0)},其中:.automatic)
tableView.reloadRows(at:changes.map {IndexPath(row:$ 0,section:0)},其中:.automatic)
tableView.endUpdates()
大小写.error(让错误):
打印(错误)
}
})。听()
  } 
 覆盖func viewWillAppear(_动画:布尔){ 
clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed
super.viewWillAppear(动画)
}
 覆盖func didReceiveMemoryWarning(){ 
super.didReceiveMemoryWarning()
//处理所有可以重新创建的资源。
}
  @objc 
func insertNewObject(_ sender:Any){
让项目:项目= Item()
item.name =“巨大”
item.save()
}
  //标记:-Segues 
  //这是一条注释 
//覆盖func prepare(用于segue:UIStoryboardSegue,发件人:任意?){
//如果segue.identifier ==“ showDetail” {
//如果让indexPath = tableView.indexPathForSelectedRow {
//让object = objects [indexPath.row]为! NSDate
//让controller =(segue.destination as!UINavigationController).topViewController as! DetailViewController
// controller.detailItem = object
// controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
// controller.navigationItem.leftItemsSupplementBackButton = true
//}
//}
//}
  // MARK:-表格视图 
 覆盖func numberOfSections(在tableView中:UITableView)-> Int { 
返回1
}
 覆盖func tableView(_ tableView:UITableView,numberOfRowsInSection部分:Int)-> Int { 
返回self.dataSource?.count? 0
}
 覆盖func tableView(_ tableView:UITableView,cellForRowAt indexPath:IndexPath)-> UITableViewCell { 
let cell = tableView.dequeueReusableCell(withIdentifier:“ Cell”,for:indexPath)
让项目:项目= self.dataSource![indexPath.row]
cell.textLabel!.text = item.name
返回单元
}
 覆盖func tableView(_ tableView:UITableView,canEditRowAt indexPath:IndexPath)->布尔{ 
//如果您不希望指定的项目可编辑,则返回false。
返回真
}
 覆盖func tableView(_ tableView:UITableView,提交editingStyle:UITableViewCellEditingStyle,forRowAt indexPath:IndexPath){ 
如果editingStyle == .delete {
self.dataSource!.removeDocument(at:indexPath.row)
}否则,如果editingStyle == .insert {
//创建适当类的新实例,将其插入数组,然后在表视图中添加新行。
}
}

  } 

霍格出来如下? 这是成功的。

让我们体验Firebase的实时性

好吧,让我们体验一下Firebase的实时感觉。
让我们点击右上角的“ +”按钮。 箱子增加了,对吗?

让我们体验Firebase的脱机性能

接下来,让我们关闭Mac WiFi并创建一个离线环境。 我们还点击右上角的“ +”按钮。
如果您可以确认模拟器的“ Hoge”增加了,请检查Firebase控制台并确保数据还没有增加。

让我们也删除数据。

您可以通过按左上方的“编辑”按钮删除数据。 删除后,确认数据从控制台中消失。

更新数据

为了更新保存的数据,我们首先修改DetailViewController 。 首先将detailItem更改为Item

  var detailItem:项目?  { 
didSet {
//更新视图。
configureView()
}
}
  func configureView(){ 
//更新明细项目的用户界面。
如果让item = detailItem {
如果让label = detailDescriptionLabel {
label.text = item.name
}
}
}

接下来,我们还修改MasterViewController 。 更改注释掉了以下内容。

 覆盖func prepare(用于segue:UIStoryboardSegue,发件人:任意?){ 
如果segue.identifier ==“ showDetail” {
如果让indexPath = tableView.indexPathForSelectedRow {
let对象:Item = self.dataSource![indexPath.row]
让controller =(segue.destination as!UINavigationController).topViewController as! DetailViewController
controller.detailItem =对象
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}

跑。 如果如下显示hoge,则表示成功。

接下来,修改Storyboard并添加TextFieldButton 。 请将DetailViewControllerIBOutletIBAction连接。

最终,DetailViewController如下所示:

 导入UIKit 
进口品
 类DetailViewController:UIViewController { 
  @IBOutlet弱var detailDescriptionLabel:UILabel! 
@IBOutlet弱var textField:UITextField!
@IBAction func updateAction(_ sender:Any){
self.detailItem?.name = self.textField.text
self.detailItem?.update()
}
  func configureView(){ 
//更新明细项目的用户界面。
如果让item = detailItem {
如果让label = detailDescriptionLabel {
label.text = item.name
}
}
}
 覆盖func viewDidLoad(){ 
super.viewDidLoad()
configureView()
}
 覆盖func didReceiveMemoryWarning(){ 
super.didReceiveMemoryWarning()
//处理所有可以重新创建的资源。
}
  var disposer:Disposer ? 
  var detailItem:项目?  { 
didSet {
//更新视图。
configureView()
self.disposer = detailItem?.listen {[弱自我](项目,错误)在
如果让错误:错误=错误{
打印(错误)
返回
}
自我?.configureView()
}
}
}
  deinit { 
self.disposer?.dispose()
}
}

如果显示在TextLabel中输入的文本,则将成功。

请注意听这里。
可以按以下方式实时监视对象。

  self.disposer = detailItem?.listen {[弱自我](项目,错误)在 
如果让错误:错误=错误{
打印(错误)
返回
}
自我?.configureView()
}

上传图片

为了创建服务,您需要上传媒体。 我将解释如何在Pring中上传图片。
请如下修改项目。 Pring可以使用文件类型上传图像。 通过将File排列为数组可以上传多张图像。

 进口品 
  @objcMembers 
类项目:对象{
 动态var名称:字符串? 
动态var图片:文件?
动态var图片:[文件] = []
  } 

接下来,请进一步修改情节提要。
将ImageView添加到DetailViewController并与IBOutlet连接

由于我们将使用UIImagePickerController选择图像,因此请如下修改DetailViewController。

点击带有viewDidLoad的ImageView,以便可以调用UIImagePicker。

 覆盖func viewDidLoad(){ 
super.viewDidLoad()
configureView()
 让tapGestureRecognizer:UITapGestureRecognizer = UITapGestureRecognizer(目标:自我,行动​​:#selector(didTapImageView)) 
self.imageView.addGestureRecognizer(tapGestureRecognizer)
self.imageView.isUserInteractionEnabled = true
}
  @objc func didTapImageView(){ 
让imagePickerController:UIImagePickerController = UIImagePickerController()
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate =自我
self.present(imagePickerController,动画:true,完成:无)
}

接下来,上传所选图像。 要上传图像,只需将文件设置为item并执行update即可

 扩展DetailViewController:UINavigationControllerDelegate,UIImagePickerControllerDelegate { 
  func imagePickerController(__ picker:UIImagePickerController,didFinishPickingMediaWithInfo信息:[String:任何]){ 
守护让图像:UIImage = info [UIImagePickerControllerOriginalImage]如? UIImage else {return}
让数据:数据= UIImageJPEGRepresentation(image,0.5)!
self.detailItem?.image = File(数据:数据)
self.uploadTasks = self.detailItem?.update()
picker.dismiss(动画:true,完成:无)
}
  } 

可以使用uploadTasks中包含的StorageUploadTask获得上传过程中的进度。

  var uploadTasks:[String:StorageUploadTask]?  { 
didSet {
让uploadTask:StorageUploadTask = uploadTasks![uploadTasks!.keys.first!]!
uploadTask.observe(.progress){(快照)在
打印(快照。进度)
}
}
}

接下来,我们还对configureView进行了一些修改。

  var donwloadTask:StorageDownloadTask? 
  func configureView(){ 
//更新明细项目的用户界面。
如果让item = detailItem {
如果让label = detailDescriptionLabel {
label.text = item.name
}
donwloadTask = item.image?.getData(完成:{[弱自我](数据,错误)在
如果让错误=错误{
打印(错误)
返回
}
self?.imageView.image = UIImage(数据:数据!)
})
}
}

最终, DetailViewController如下所示:

 导入UIKit 
导入Firebase
进口品
 类DetailViewController:UIViewController { 
  @IBOutlet弱var detailDescriptionLabel:UILabel! 
@IBOutlet弱var imageView:UIImageView!
@IBOutlet弱var textField:UITextField!
@IBAction func updateAction(_ sender:Any){
self.detailItem?.name = self.textField.text
self.detailItem?.update()
}
  var uploadTasks:[String:StorageUploadTask]?  { 
didSet {
让uploadTask:StorageUploadTask = uploadTasks![uploadTasks!.keys.first!]!
uploadTask.observe(.progress){(快照)在
打印(快照。进度)
}
}
}
  var donwloadTask:StorageDownloadTask? 
  func configureView(){ 
//更新明细项目的用户界面。
如果让item = detailItem {
如果让label = detailDescriptionLabel {
label.text = item.name
}
donwloadTask = item.image?.getData(完成:{[弱自我](数据,错误)在
如果让错误=错误{
打印(错误)
返回
}
self?.imageView.image = UIImage(数据:数据!)
})
}
}
 覆盖func viewDidLoad(){ 
super.viewDidLoad()
configureView()
 让tapGestureRecognizer:UITapGestureRecognizer = UITapGestureRecognizer(目标:自我,行动​​:#selector(didTapImageView)) 
self.imageView.addGestureRecognizer(tapGestureRecognizer)
self.imageView.isUserInteractionEnabled = true
}
  @objc func didTapImageView(){ 
让imagePickerController:UIImagePickerController = UIImagePickerController()
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate =自我
self.present(imagePickerController,动画:true,完成:无)
}
 覆盖func didReceiveMemoryWarning(){ 
super.didReceiveMemoryWarning()
//处理所有可以重新创建的资源。
}
  var disposer:Disposer ? 
  var detailItem:项目?  { 
didSet {
//更新视图。
configureView()
self.disposer = detailItem?.listen {[弱自我](项目,错误)在
如果让错误:错误=错误{
打印(错误)
返回
}
自我?.configureView()
}
}
}
  deinit { 
self.disposer?.dispose()
}
}
 扩展DetailViewController:UINavigationControllerDelegate,UIImagePickerControllerDelegate { 
  func imagePickerController(__ picker:UIImagePickerController,didFinishPickingMediaWithInfo信息:[String:任何]){ 
守护让图像:UIImage = info [UIImagePickerControllerOriginalImage]如? UIImage else {return}
让数据:数据= UIImageJPEGRepresentation(image,0.5)!
self.detailItem?.image = File(数据:数据)
self.uploadTasks = self.detailItem?.update()
picker.dismiss(动画:true,完成:无)
}
}

跑。

如果点击ImageView并选择一个图像,则应在几秒钟内上传该图像。 并且,如果图像显示在ImageView中,则表示成功。

另外,让我们看一下打印时的进度显示。

这样, Pring提供了功能强大的功能,可帮助开发App开发所需的功能。

https://github.com/1amageek/Pring