swift通知栏推送
by Neo Ighodaro
由新Ighodaro
如何使用Swift使用推送通知构建食品交付应用 (How to build a food delivery app with push notifications using Swift)
A basic understanding of Swift and Node.js is needed to follow this tutorial.
要学习本教程,需要对Swift和Node.js有基本的了解。
Last mile delivery marketplaces make it easy to order food from a mobile device and have it delivered to a user’s door while it’s still hot.
最后一英里的运送市场使从移动设备订购食物变得很容易,并且可以在仍然很热的时候将食物运送到用户家中。
Marketplaces like Deliveroo, Postmates, or Uber Eats use your device’s location to serve you a list of restaurants that are close enough to you (and open) so you can get your delivery as soon as possible.
像Deliveroo,Postmates或Uber Eats这样的市场都使用您设备的位置为您提供了一个距离您足够近(开着)的餐馆列表,因此您可以尽快获得送货。
This realtime experience between the customer, restaurant, and driver relies on transactional push notifications to move the order from the kitchen to the table seamlessly. Customers want push notifications to alert them when their order is on its way and when they need to meet the driver at the door.
客户,餐厅和驾驶员之间的这种实时体验依赖于交易推送通知,以将订单从厨房无缝地转移到桌子上。 客户希望推送通知在他们的订单即将到来时以及何时需要在门口与驾驶员见面时向他们发出警报。
Setting up push notifications can be confusing and time consuming. However, with Pusher’s Push Notifications API, the process is a lot easier and faster.
设置推送通知可能会造成混乱和耗时。 但是,使用Pusher的Push Notifications API ,此过程变得更加轻松快捷。
In this article, we will be considering how you can build apps on iOS that have transactional push notifications. For this, we will be building a make-believe food delivery app.
在本文中,我们将考虑如何在具有事务性推送通知的iOS上构建应用程序。 为此,我们将构建一个虚构的外卖应用程序。
先决条件 (Prerequisites)
A Mac with Xcode installed. Download Xcode here.
装有Xcode的Mac。 在此处下载Xcode 。
- Knowledge of using Xcode. 了解使用Xcode的知识。
Knowledge of Swift.
了解Swift 。
A Pusher account. Create one here.
Pusher帐户。 在此处创建一个 。
Basic knowledge of JavaScript/Node.js (Check out this tutorial).
JavaScript / Node.js的基础知识( 请参阅本教程 )。
Cocoapods installed on your machine.
您的机器上已安装了 Cocoapods。
Once you have the requirements, let’s start.
有了需求后,就开始吧。
构建我们的应用程序-规划 (Building our application — planning)
Before we start building our application, we need to do some planning on how we want the application to work.
在开始构建应用程序之前,我们需要对我们希望应用程序的工作方式进行一些规划。
We will be making three applications:
我们将提出三个申请:
- The backend application (Web using Node.js). 后端应用程序(使用Node.js的Web)。
- The client application (iOS using Swift). 客户端应用程序(使用Swift的iOS)。
- The admin application (iOS using Swift). 管理应用程序(使用Swift的iOS)。
后端应用 (The backend application)
This will be the API. For simplicity, we will not add any sort of authentication to the API. We will be calling the API from our iOS applications. The API should be able to provide the food inventory, the orders, and also manage the orders. We will also be sending push notifications from the backend application.
这将是API。 为简单起见,我们不会向API添加任何形式的身份验证。 我们将从iOS应用程序中调用API。 该API应该能够提供食品库存,订单以及管理订单。 我们还将从后端应用程序发送推送通知。
客户端应用 (The client application)
This will be the application that will be with the customer. It’s where the user will be able to order food. For simplicity, we will not have any sort of authentication, and everything will be straight to the point. A customer should be able to see the inventory and order one or more things from that inventory. They should also be able to see the list of their orders and the status of each order.
这将是与客户一起使用的应用程序。 用户可以在这里订购食物。 为简单起见,我们将没有任何形式的身份验证,并且所有内容都直截了当。 客户应该能够看到库存并从该库存中订购一件或多件东西。 他们还应该能够看到他们的订单列表以及每个订单的状态。
管理员应用程序 (The admin application)
This will be the application that the company providing the service will use to fulfill orders. The application will display the available orders, and the admin will be able to set the status for each order.
这将是提供服务的公司用于履行订单的应用程序。 该应用程序将显示可用的订单,管理员将能够设置每个订单的状态。
构建后端应用程序(API) (Building the backend application (API))
The first thing we want to build is the API. We will be adding everything required to support our iOS applications, and will then add push notifications later on.
我们要构建的第一件事是API。 我们将添加支持我们的iOS应用程序所需的所有内容,然后在以后添加推送通知。
To get started, create a project directory for the API. In the directory, create a new file called package.json
. In the file, paste the following:
首先,为API创建一个项目目录。 在目录中,创建一个名为package.json
的新文件。 在文件中,粘贴以下内容:
{ "main": "index.js", "scripts": {}, "dependencies": { "body-parser": "^1.18.2", "express": "^4.16.2" } }
Next run the command below in your terminal:
接下来在您的终端中运行以下命令:
$ npm install
This will install all the listed dependencies. Next, create an index.js
file in the same directory as the package.json
file and paste in the following code:
这将安装所有列出的依赖项。 接下来,在与package.json
文件相同的目录中创建一个index.js
文件,并粘贴以下代码:
// -------------------------------------------------------- // Pull in the libraries // -------------------------------------------------------- const app = require('express')() const bodyParser = require('body-parser') // -------------------------------------------------------- // Helpers // -------------------------------------------------------- function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } // -------------------------------------------------------- // In-memory database // -------------------------------------------------------- var user_id = null var orders = [] let inventory = [ { id: uuidv4(), name: "Pizza Margherita", description: "Features tomatoes, sliced mozzarella, basil, and extra virgin olive oil.", amount: 39.99, image: 'pizza1' }, { id: uuidv4(), name: "Bacon cheese fry", description: "Features tomatoes, bacon, cheese, basil and oil", amount: 29.99, image: 'pizza2' } ] // -------------------------------------------------------- // Express Middlewares // -------------------------------------------------------- app.use(bodyParser.json()) app.use(bodyParser.urlencoded({extended: false})) // -------------------------------------------------------- // Routes // -------------------------------------------------------- app.get('/orders', (req, res) => res.json(orders)) app.post('/orders', (req, res) => { let id = uuidv4() user_id = req.body.user_id let pizza = inventory.find(item => item["id"] === req.body.pizza_id) if (!pizza) { return res.json({status: false}) } orders.unshift({id, user_id, pizza, status: "Pending"}) res.json({status: true}) }) app.put('/orders/:id', (req, res) => { let order = orders.find(order => order["id"] === req.params.id) if ( ! order) { return res.json({status: false}) } orders[orders.indexOf(order)]["status"] = req.body.status return res.json({status: true}) }) app.get('/inventory', (req, res) => res.json(inventory)) app.get('/', (req, res) => res.json({status: "success"})) // -------------------------------------------------------- // Serve application // -------------------------------------------------------- app.listen(4000, _ => console.log('App listening on port 4000!'))
The above code is a simple Express application. Everything is self-explanatory and has comments to guide you.
上面的代码是一个简单的Express应用程序。 一切都是不言自明的,并有注释可以指导您。
In the first route, /orders
, we display the list of orders available from the in-memory data store. In the second route, POST /orders
, we just add a new order to the list of orders
. In the third route, PUT /orders/:id
, we just modify the status of a single order from the list of orders
. In the fourth route, GET /inventory
, we list the inventory available from the list of inventory
in the database.
在第一个路线/orders
,我们显示内存数据存储中可用的订单列表。 在第二条路线POST /orders
,我们只是将新订单添加到orders
列表中。 在第三条路线PUT /orders/:id
,我们只是从orders
列表中修改单个订单的状态。 在第四条路线GET /inventory
,我们从数据库中的清单列表中列出可用inventory
。
We are done with the API for now, and we will revisit it when we need to add the push notification code. If you want to test that the API is working, then run the following command on your terminal:
目前,我们已经完成了API的使用,当我们需要添加推送通知代码时,我们将对其进行重新访问。 如果要测试API是否正常运行,请在终端上运行以下命令:
$ node index.js
This will start a new Node server listening on port 4000.
这将启动在端口4000上侦听的新Node服务器。
构建客户端应用程序 (Building the client application)
The next thing we need to do is build the client application in Xcode. To start, launch Xcode and create a new ‘Single Application’ project. We will name our project PizzaareaClient.
我们需要做的下一步是在Xcode中构建客户端应用程序。 首先,启动Xcode并创建一个新的“单一应用程序”项目。 我们将项目命名为PizzaareaClient。
Once the project has been created, exit Xcode and create a new file called Podfile
in the root of the Xcode project you just created. In the file, paste in the following code:
创建项目后,退出Xcode并在刚创建的Xcode项目的根目录中创建一个名为Podfile
的新文件。 在文件中,粘贴以下代码:
platform :ios, '11.0'
target 'PizzareaClient' do use_frameworks! pod 'PusherSwift', '~> 5.1.1' pod 'Alamofire', '~> 4.6.0' end
In the file above, we specified the dependencies the project needs to run. Remember to change the target
above to the name of your project. Now in your terminal, run the following command to install the dependencies:
在上面的文件中,我们指定了项目需要运行的依赖项。 请记住,将 上面 的 target
更改 为您的项目名称。 现在在您的终端中,运行以下命令以安装依赖项:
$ pod install
After the installation is complete, open the Xcode workspace file that was generated by Cocoapods. This should relaunch Xcode.
安装完成后,打开由Cocoapods生成的Xcode工作区文件。 这应该重新启动Xcode。
When Xcode has been relaunched, open the Main.storyboard
file. In it we will create the storyboard for our client application. Below is a screenshot of how we have designed our storyboard:
Xcode重新启动后,打开Main.storyboard
文件。 在其中,我们将为客户应用程序创建情节提要。 以下是我们如何设计故事板的屏幕截图:
The first scene is the navigation view controller, which has a table view controller as the root controller. The navigation controller is the initial controller that is loaded when the application is launched.
第一个场景是导航视图控制器,它具有一个表视图控制器作为根控制器。 导航控制器是启动应用程序时加载的初始控制器。
创建披萨列表场景 (Creating the pizza list scene)
The second scene is the view controller that lists the inventory that we have available.
第二个场景是视图控制器,它列出了我们可用的清单。
Create a new file in Xcode called PizzaTableListViewController.swift
, make it the custom class for the second scene, and paste in the following code:
在Xcode中创建一个名为PizzaTableListViewController.swift
的新文件,使其成为第二个场景的自定义类,然后粘贴以下代码:
import UIKit import Alamofire
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() { super.viewDidLoad()
navigationItem.title = "Select Pizza"
fetchInventory { pizzas in guard pizzas != nil else { return } self.pizzas = pizzas! self.tableView.reloadData() } }
private func fetchInventory(completion: @escaping ([Pizza]?) -> Void) { Alamofire.request("http://127.0.0.1:4000/inventory", method: .get) .validate() .responseJSON { response in guard response.result.isSuccess else { return completion(nil) } guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.flatMap { pizzaDict -> Pizza? in var data = pizzaDict! data["image"] = UIImage(named: pizzaDict!["image"] as! String)
return Pizza(data: data) }
completion(inventory) } }
@IBAction func ordersButtonPressed(_ sender: Any) { performSegue(withIdentifier: "orders", sender: nil) }
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return pizzas.count }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
cell.name.text = pizzas[indexPath.row].name cell.imageView?.image = pizzas[indexPath.row].image cell.amount.text = "$\(pizzas[indexPath.row].amount)" cell.miscellaneousText.text = pizzas[indexPath.row].description
return cell }
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 100.0 }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "pizza", sender: self.pizzas[indexPath.row] as Pizza) }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "pizza" { guard let vc = segue.destination as? PizzaViewController else { return } vc.pizza = sender as? Pizza } } }
In the viewDidLoad
method, we call the fetchInventory
method that uses Alamofire
to fetch the inventory from our backend API. Then we save the response to the orders
property of the controller.
在viewDidLoad
方法中,我们调用fetchInventory
方法,该方法使用Alamofire
从后端API中获取清单。 然后,将响应保存到控制器的orders
属性。
The ordersButtonPressed
is linked to the Orders
button on the scene. This just presents the scene with the list of orders using a named segue orders
.
ordersButtonPressed
链接到场景中的“ Orders
按钮。 这只是呈现使用名为赛格瑞订单列表现场orders
。
The tableView*
methods implement methods available to the UITableViewDelegate
protocol and should be familiar to you.
tableView*
方法实现了UITableViewDelegate
协议可用的方法,您应该熟悉它们。
The final method prepare
simply sends the pizza
to the view controller on navigation. But this pizza
is only sent over if the view controller being loaded is the PizzaViewController
.
最终的prepare
方法只是将pizza
发送到导航中的视图控制器。 但这种pizza
只送过来,如果要加载的视图控制器是PizzaViewController
。
Before we create the third scene, create a PizzaTableViewCell.swift
class and paste in the following:
在创建第三个场景之前,请创建PizzaTableViewCell.swift
类并粘贴以下内容:
import UIKit class PizzaTableViewCell: UITableViewCell { @IBOutlet weak var pizzaImageView: UIImageView! @IBOutlet weak var name: UILabel! @IBOutlet weak var miscellaneousText: UILabel! @IBOutlet weak var amount: UILabel! override func awakeFromNib() { super.awakeFromNib() } }
⚠️ Make sure the custom class of the cells in the second scene is
PizzaTableViewCell
, and that the reusable identifier isPizza
.⚠️确保第二个场景中单元格的自定义类是
PizzaTableViewCell
,并且可重复使用的标识符是Pizza
。
创建披萨视图场景 (Creating the pizza view scene)
The third scene in our storyboard is the Pizza view scene. This is where the selected inventory can be viewed.
我们的情节提要中的第三个场景是Pizza视图场景。 在这里可以查看所选库存。
Create a PizzaViewController.swift
file, make it the custom class for the scene above, and paste in the following code:
创建一个PizzaViewController.swift
文件,使其成为上面场景的自定义类,然后粘贴以下代码:
import UIKit import Alamofire
class PizzaViewController: UIViewController {
var pizza: Pizza?
@IBOutlet weak var amount: UILabel! @IBOutlet weak var pizzaDescription: UILabel! @IBOutlet weak var pizzaImageView: UIImageView!
override func viewDidLoad() { super.viewDidLoad()
navigationItem.title = pizza!.name pizzaImageView.image = pizza!.image pizzaDescription.text = pizza!.description amount.text = "$\(String(describing: pizza!.amount))" }
@IBAction func buyButtonPressed(_ sender: Any) { let parameters = [ "pizza_id": pizza!.id, "user_id": AppMisc.USER_ID ]
Alamofire.request("http://127.0.0.1:4000/orders", method: .post, parameters: parameters) .validate() .responseJSON { response in guard response.result.isSuccess else { return self.alertError() }
guard let status = response.result.value as? [String: Bool], let successful = status["status"] else { return self.alertError() }
successful ? self.alertSuccess() : self.alertError() } }
private func alertError() { return self.alert( title: "Purchase unsuccessful!", message: "Unable to complete purchase please try again later." ) }
private func alertSuccess() { return self.alert( title: "Purchase Successful", message: "You have ordered successfully, your order will be confirmed soon." ) }
private func alert(title: String, message: String) { let alertCtrl = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertCtrl.addAction(UIAlertAction(title: "Okay", style: .cancel) { action in self.navigationController?.popViewController(animated: true) })
present(alertCtrl, animated: true, completion: nil) } }
In the code above, we have multiple @IBOutlet
’s and a single @IBAction
. You need to link the outlets and actions to the controller from the storyboard.
在上面的代码中,我们有多个@IBOutlet
和一个@IBAction
。 您需要从情节提要中将插座和动作链接到控制器。
In the viewDidLoad
we set the outlets so they display the correct values using the pizza
sent from the previous view controller. The buyButtonPressed
method uses Alamofire
to place an order by sending a request to the API. The remaining methods handle displaying the error or success response from the API.
在viewDidLoad
我们设置出口,以便它们使用从前一个视图控制器发送的pizza
显示正确的值。 buyButtonPressed
方法使用Alamofire
通过向API发送请求来下订单。 其余方法处理显示来自API的错误或成功响应。
创建订单清单场景 (Creating the orders list scene)
The next scene is the Orders list scene. In this scene, all the orders are listed so the user can see them and their status:
下一个场景是“订单”列表场景。 在此场景中,列出了所有订单,因此用户可以查看它们及其状态:
Create a OrderTableViewController.swift
file, make it the custom class for the scene above, and paste in the following code:
创建一个OrderTableViewController.swift
文件,使其成为上面场景的自定义类,然后粘贴以下代码:
import UIKit import Alamofire
class OrdersTableViewController: UITableViewController {
var orders: [Order] = []
override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Orders"
fetchOrders { orders in self.orders = orders! self.tableView.reloadData() } }
private func fetchOrders(completion: @escaping([Order]?) -> Void) { Alamofire.request("http://127.0.0.1:4000/orders").validate().responseJSON { response in guard response.result.isSuccess else { return completion(nil) }
guard let rawOrders = response.result.value as? [[String: Any]?] else { return completion(nil) }
let orders = rawOrders.flatMap { ordersDict -> Order? in guard let orderId = ordersDict!["id"] as? String, let orderStatus = ordersDict!["status"] as? String, var pizza = ordersDict!["pizza"] as? [String: Any] else { return nil }
pizza["image"] = UIImage(named: pizza["image"] as! String)
return Order( id: orderId, pizza: Pizza(data: pizza), status: OrderStatus(rawValue: orderStatus)! ) }
completion(orders) } }
@IBAction func closeButtonPressed(_ sender: Any) { dismiss(animated: true, completion: nil) }
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return orders.count }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "order", for: indexPath) let order = orders[indexPath.row]
cell.textLabel?.text = order.pizza.name cell.imageView?.image = order.pizza.image cell.detailTextLabel?.text = "$\(order.pizza.amount) - \(order.status.rawValue)"
return cell }
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 100.0 }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "order", sender: orders[indexPath.row] as Order) }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "order" { guard let vc = segue.destination as? OrderViewController else { return } vc.order = sender as? Order } } }
The code above is similar to the code in the PizzaTableViewController
above. However, instead of fetching the inventory, it fetches the orders
. Instead of passing the pizza
in the last method, it passes the order
to the next controller. The controller also comes with a closeButtonPressed
method that just dismisses the controller and returns to the inventory list scene.
上面的代码PizzaTableViewController
上面的PizzaTableViewController
的代码。 但是,它不是获取库存,而是获取orders
。 而不是通过最后一个方法传递pizza
,而是将order
传递给下一个控制器。 控制器还带有closeButtonPressed
方法,该方法只是关闭控制器并返回到清单清单场景。
创建订单状态场景 (Creating the Order Status Scene)
The next scene is the Order scene. In this scene, we can see the status of the order:
下一个场景是订购场景。 在此场景中,我们可以看到订单的状态:
⚠️ The scene above has an invisible view right above the status label. You need to use this view to create an
@IBOutlet
to the controller.above️上面的场景在状态标签的正上方具有不可见的视图。 您需要使用此视图为控制器创建
@IBOutlet
。
Create a OrderViewController.swift
file, make it the custom class for the scene above, and paste in the following code:
创建一个OrderViewController.swift
文件,使其成为上面场景的自定义类,然后粘贴以下代码:
import UIKit
class OrderViewController: UIViewController {
var order: Order?
@IBOutlet weak var status: UILabel! @IBOutlet weak var activityView: ActivityIndicator!
override func viewDidLoad() { super.viewDidLoad()
navigationItem.title = order?.pizza.name
activityView.startLoading()
switch order!.status { case .pending: status.text = "Processing Order" case .accepted: status.text = "Preparing Order" case .dispatched: status.text = "Order is on its way!" case .delivered: status.text = "Order delivered" activityView.strokeColor = UIColor.green activityView.completeLoading(success: true) } } }
In the code above, we are doing all the work in our viewDidLoad
method. In there we have the ActivityIndicator
class, which we will create next, referenced as an @IBOutlet
.
在上面的代码中,我们正在执行viewDidLoad
方法中的所有工作。 在那里,我们有ActivityIndicator
类,接下来将创建该类,将其称为@IBOutlet
。
创建应用程序的其他部分 (Creating other parts of the application)
We are using a third-party library called the [ActivityIndicator](https://github.com/abdulKarim002/activityIndicator)
, but since the package is not available via Cocoapods, we have opted to create it ourselves and import it.
我们正在使用一个名为[ActivityIndicator](https://github.com/abdulKarim002/activityIndicator)
的第三方库,但是由于该软件包无法通过Cocoapods获得,因此我们选择自己创建并导入。
Create a new file in Xcode called ActivityIndicator
and paste the code from the repo here into it.
在Xcode中创建一个名为ActivityIndicator
的新文件,并将代码从仓库中粘贴到其中。
Next, create a new Order.swift
file and paste in the following code:
接下来,创建一个新的Order.swift
文件并粘贴以下代码:
import Foundation
struct Order { let id: String let pizza: Pizza var status: OrderStatus }
enum OrderStatus: String { case pending = "Pending" case accepted = "Accepted" case dispatched = "Dispatched" case delivered = "Delivered" }
Finally, create a Pizza.swift
and paste in the following code:
最后,创建Pizza.swift
并粘贴以下代码:
import UIKit
struct Pizza { let id: String let name: String let description: String let amount: Float let image: UIImage
init(data: [String: Any]) { self.id = data["id"] as! String self.name = data["name"] as! String self.amount = data["amount"] as! Float self.description = data["description"] as! String self.image = data["image"] as! UIImage } }
That is all for the client application. One last thing we need to do, though, is modify the info.plist
file. We need to add an entry to the plist
file to allow connection to our local server:
这就是客户端应用程序的全部内容。 但是,我们需要做的最后一件事是修改info.plist
文件。 我们需要在plist
文件中添加一个条目,以允许连接到我们的本地服务器:
Let’s move on to the admin application.
让我们继续进行管理应用程序。
生成管理应用程序 (Building the admin application)
Launch a new instance of Xcode and create a new ‘Single Application’ project. We will name our project PizzaareaAdmin.
启动Xcode的新实例并创建一个新的“单一应用程序”项目。 我们将我们的项目命名为PizzaareaAdmin。
Once the project has been created, exit Xcode and create a new file called Podfile
in the root of the Xcode project you just created. In the file, paste in the following code:
创建项目后,退出Xcode并在刚创建的Xcode项目的根目录中创建一个名为Podfile
的新文件。 在文件中,粘贴以下代码:
platform :ios, '11.0'
target 'PizzareaAdmin' do use_frameworks! pod 'PusherSwift', '~> 5.1.1' pod 'Alamofire', '~> 4.6.0' end
In the file above, we specified the dependencies the project needs to run. Remember to change the **target**
above to the name of your project.
在上面的文件中,我们指定了项目需要运行的依赖项。 请记住,将 上面的“ **target**
更改 为您的项目名称。
Now, in your terminal, run the following command to install the dependencies:
现在,在您的终端中,运行以下命令以安装依赖项:
$ pod install
After the installation is complete, open the Xcode workspace file that was generated by Cocoapods. This should relaunch Xcode.
安装完成后,打开由Cocoapods生成的Xcode工作区文件。 这应该重新启动Xcode。
When Xcode has been relaunched, open the Main.storyboard
file. In there we will create the storyboard for our client application. Below is a screenshot of how we have designed our storyboard:
Xcode重新启动后,打开Main.storyboard
文件。 在这里,我们将为客户应用程序创建情节提要。 以下是我们如何设计故事板的屏幕截图:
Above we have a navigation view controller that is the initial view controller.
上面我们有一个导航视图控制器,它是初始视图控制器。
创建订单清单场景 (Creating the orders list scene)
The orders list scene is supposed to show the list of clients’ orders. From there we can change the status of each order when we want.
订单列表场景应该显示客户订单的列表。 从那里我们可以在需要时更改每个订单的状态。
Create a new file in Xcode called OrdersListViewController.swift
, make it the custom class for the second scene, and paste in the following code:
在Xcode中创建一个名为OrdersListViewController.swift
的新文件,使其成为第二个场景的自定义类,并粘贴以下代码:
import UIKit import Alamofire
class OrdersTableViewController: UITableViewController {
var orders: [Order] = []
override func viewDidLoad() { super.viewDidLoad()
navigationItem.title = "Client Orders"
fetchOrders { orders in self.orders = orders! self.tableView.reloadData() } }
private func fetchOrders(completion: @escaping([Order]?) -> Void) { Alamofire.request("http://127.0.0.1:4000/orders").validate().responseJSON { response in guard response.result.isSuccess else { return completion(nil) }
guard let rawOrders = response.result.value as? [[String: Any]?] else { return completion(nil) }
let orders = rawOrders.flatMap { ordersDict -> Order? in guard let orderId = ordersDict!["id"] as? String, let orderStatus = ordersDict!["status"] as? String, var pizza = ordersDict!["pizza"] as? [String: Any] else { return nil }
pizza["image"] = UIImage(named: pizza["image"] as! String)
return Order( id: orderId, pizza: Pizza(data: pizza), status: OrderStatus(rawValue: orderStatus)! ) }
completion(orders) } }
override func numberOfSections(in tableView: UITableView) -> Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return orders.count }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "order", for: indexPath) let order = orders[indexPath.row]
cell.textLabel?.text = order.pizza.name cell.imageView?.image = order.pizza.image cell.detailTextLabel?.text = "$\(order.pizza.amount) - \(order.status.rawValue)"
return cell }
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 100.0 }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let order: Order = orders[indexPath.row]
let alertCtrl = UIAlertController( title: "Change Status", message: "Change the status of the order based on the progress made.", preferredStyle: .actionSheet )
alertCtrl.addAction(createActionForStatus(.pending, order: order)) alertCtrl.addAction(createActionForStatus(.accepted, order: order)) alertCtrl.addAction(createActionForStatus(.dispatched, order: order)) alertCtrl.addAction(createActionForStatus(.delivered, order: order)) alertCtrl.addAction(createActionForStatus(nil, order: nil))
present(alertCtrl, animated: true, completion: nil) }
private func createActionForStatus(_ status: OrderStatus?, order: Order?) -> UIAlertAction { let alertTitle = status == nil ? "Cancel" : status?.rawValue let alertStyle: UIAlertActionStyle = status == nil ? .cancel : .default
let action = UIAlertAction(title: alertTitle, style: alertStyle) { action in if status != nil { self.setStatus(status!, order: order!) } }
if status != nil { action.isEnabled = status?.rawValue != order?.status.rawValue }
return action }
private func setStatus(_ status: OrderStatus, order: Order) { updateOrderStatus(status, order: order) { successful in guard successful else { return } guard let index = self.orders.index(where: {$0.id == order.id}) else { return }
self.orders[index].status = status self.tableView.reloadData() } }
private func updateOrderStatus(_ status: OrderStatus, order: Order, completion: @escaping(Bool) -> Void) { let url = "http://127.0.0.1:4000/orders/" + order.id let params = ["status": status.rawValue]
Alamofire.request(url, method: .put, parameters: params).validate().responseJSON { response in guard response.result.isSuccess else { return completion(false) } guard let data = response.result.value as? [String: Bool] else { return completion(false) }
completion(data["status"]!) } } }
The code above is similar to the code in the PizzaListTableViewController
in the client application, so check back there if you need further explanation.
上面的代码与客户端应用程序中的PizzaListTableViewController
中的代码相似,因此如果需要进一步的说明,请在此处检查。
There is a createActionForStatus
, which is a helper for creating and configuring UIAlertAction
object. There is a setStatus
method that just attempts to set the status for an order. And then there is the updateOrderStatus
method that sends the update request using Alamofire
to the API.
有一个createActionForStatus
,它是用于创建和配置UIAlertAction
对象的帮助器。 有一个setStatus
方法只是尝试设置订单的状态。 然后是updateOrderStatus
方法,该方法使用Alamofire
将更新请求发送到API。
Next, create the Order.swift
and Pizza.swift
classes like we did before in the client application:
接下来,像以前在客户端应用程序中一样创建Order.swift
和Pizza.swift
类:
// Order.swift import Foundation
struct Order { let id: String let pizza: Pizza var status: OrderStatus }
enum OrderStatus: String { case pending = "Pending" case accepted = "Accepted" case dispatched = "Dispatched" case delivered = "Delivered" }
// Pizza.swift import UIKit
struct Pizza { let id: String let name: String let description: String let amount: Float let image: UIImage
init(data: [String: Any]) { self.id = data["id"] as! String self.name = data["name"] as! String self.amount = data["amount"] as! Float self.description = data["description"] as! String self.image = data["image"] as! UIImage } }
That’s all for the admin application. One last thing we need to do, though, is modify the info.plist
file as we did in the client application.
这就是管理应用程序的全部内容。 但是,我们需要做的最后一件事是像在客户端应用程序中一样修改info.plist
文件。
向我们的送餐iOS应用添加推送通知 (Adding Push Notifications to our food delivery iOS app)
At this point, the application works as expected out of the box. We now need to add push notifications to the application to make it more engaging even when the user is not currently using the application.
此时,该应用程序可以按预期工作。 现在,我们需要向应用程序添加推送通知,以使其更具吸引力,即使用户当前未使用该应用程序也是如此。
⚠️ You need to be enrolled to the Apple Developer program to be able to use the Push Notifications feature. Also, Push Notifications do not work on Simulators, so you will need an actual iOS device to test.
⚠️您需要注册到Apple Developer程序才能使用Push Notifications功能。 此外,推送通知在模拟器上也不起作用,因此您将需要实际的iOS设备进行测试。
Pusher’s Push Notifications API has first-class support for native iOS applications. Your iOS app instances subscribe to I**nterests**, then your servers send push notifications to those interests. Every app instance subscribed to that interest will receive the notification, even if the app is not open on the device at the time.
Pusher的Push Notifications API对本地iOS应用程序具有一流的支持。 您的iOS应用实例订阅了I ** nterests **,然后您的服务器向这些兴趣发送推送通知。 订阅该兴趣的每个应用程序实例都会收到通知,即使该应用程序当时不在设备上打开。
This section describes how you can set up an iOS app to receive transactional push notifications about your food delivery orders through Pusher.
本节介绍如何设置iOS应用程序以通过Pusher接收有关您的食品交付订单的事务性推送通知。
配置APN (Configure APNs)
Pusher relies on the Apple Push Notification service (APNs) to deliver push notifications to iOS application users on your behalf. When we deliver push notifications, we use your APNs Key. This page guides you through the process of getting an APNs Key and how to provide it to Pusher.
Pusher依靠Apple Push Notification Service(APN)来代表您向iOS应用程序用户传递推送通知。 当我们传递推送通知时,我们将使用您的APNs密钥。 本页指导您完成获取APNs密钥的过程,以及如何将其提供给Pusher。
Head over to the Apple Developer dashboard by clicking here and then create a new Key as seen below:
通过单击此处转到Apple Developer仪表板,然后创建一个新密钥,如下所示:
When you have created the key, download it. Keep it safe as we will need it in the next section.
创建密钥后,请下载它。 确保安全,因为我们将在下一节中使用它。
⚠️ You have to keep the generated key safe as you cannot get it back if you lose it.
⚠️您必须保护生成的密钥安全,因为如果丢失它将无法找回。
创建您的Pusher应用程序 (Creating your Pusher application)
The next thing you need to do is create a new Pusher Push Notification application from the Pusher dashboard.
接下来需要做的是从Pusher仪表板创建一个新的Pusher Push Notification应用程序。
When you have created the application, you should be presented with a Quickstart wizard that will help you set up the application.
创建应用程序后,应显示一个快速入门向导,该向导将帮助您设置应用程序。
In order to configure Push Notifications, you will need to get an APNs key from Apple. This is the same key as the one we downloaded in the previous section. Once you’ve got the key, upload it to the Quickstart wizard.
为了配置推送通知,您将需要从Apple获得APNs密钥。 这与上一节中下载的密钥相同。 获取密钥后,将其上传到快速入门向导。
Enter your Apple Team ID. You can get the Team ID from here. Click on continue to proceed to the next step.
输入您的Apple Team ID。 您可以从此处获取团队ID。 单击继续以继续下一步。
更新客户端应用程序以支持推送通知 (Updating your client application to support Push Notifications)
In your client application, open the Podfile
and add the following pod to the list of dependencies:
在您的客户端应用程序中,打开Podfile
并将以下pod添加到依赖项列表中:
pod 'PushNotifications'
Now run the pod install
command as you did earlier to pull in the notifications package. When installation is complete, create a new class AppMisc.swift
and in there paste the following:
现在,像以前一样运行pod install
命令,以获取通知包。 安装完成后,创建一个新的AppMisc.swift
类,并在其中粘贴以下内容:
class AppMisc { static let USER_ID = NSUUID().uuidString.replacingOccurrences(of: "-", with: "_") }
In the little class above, we generate a user ID for the session. In a real application, you would typically have an actual user ID after authentication.
在上面的小类中,我们为会话生成一个用户ID。 在实际的应用程序中,身份验证后通常会具有实际的用户ID。
Next open the AppDelegate
class and import the PushNotifications
package:
接下来打开AppDelegate
类并导入PushNotifications
包:
import PushNotifications
Now, as part of the AppDelegate
class, add the following:
现在,作为AppDelegate
类的一部分,添加以下内容:
let pushNotifications = PushNotifications.shared
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { self.pushNotifications.start(instanceId: "PUSHER_NOTIF_INSTANCE_ID") self.pushNotifications.registerForRemoteNotifications() return true }
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { self.pushNotifications.registerDeviceToken(deviceToken) { try? self.pushNotifications.subscribe(interest: "orders_" + AppMisc.USER_ID) } }
? Replace P
USHER_PUSH_NOTIF_INSTANCE_ID
with the key given to you by the Pusher application.? 将P
USHER_PUSH_NOTIF_INSTANCE_ID
替换为Pusher应用程序提供给您的密钥。
In the code above, we set up push notifications in the application(didFinishLaunchingWithOptions:)
method and then we subscribe in the application(didRegisterForRemoteNotificationsWithDeviceToken:)
method.
在上面的代码中,我们在application(didFinishLaunchingWithOptions:)
方法中设置了推送通知,然后在application(didRegisterForRemoteNotificationsWithDeviceToken:)
方法中进行了订阅。
Next, we need to enable push notifications for the application. In the project navigator, select your project, and click on the Capabilities tab. Enable Push Notifications by turning the switch ON.
接下来,我们需要为应用程序启用推送通知。 在项目导航器中,选择您的项目,然后单击“ 功能”选项卡。 通过打开开关启用推送通知 。
更新您的管理应用程序以支持推送通知 (Updating your admin application to support Push Notifications)
Your admin application also needs to be able to receive Push Notifications. The process is similar to the set up above. The only difference will be the interest we will be subscribing to in AppDelegate
which will be orders.
您的管理应用程序还需要能够接收推送通知。 该过程类似于上面的设置。 唯一的区别是我们将在AppDelegate
中订阅的兴趣是订单 。
更新您的API以发送推送通知 (Updating your API to send Push Notifications)
Push Notifications will be published using our backend server API, which is written in Node.js. For this we will use the Node.js SDK. cd
to the backend project directory and run the following command:
推送通知将使用我们的后端服务器API发布,该API用Node.js编写。 为此,我们将使用Node.js SDK 。 cd
到后端项目目录,然后运行以下命令:
$ npm install pusher-push-notifications-node --save
Next, open the index.js
file and import the pusher-push-notifications-node
package:
接下来,打开index.js
文件并导入pusher-push-notifications-node
包:
const PushNotifications = require('pusher-push-notifications-node');
let pushNotifications = new PushNotifications({ instanceId: 'PUSHER_PUSH_NOTIF_INSTANCE_ID', secretKey: 'PUSHER_PUSH_NOTIF_SECRET_KEY' });
Next, we want to add a helper function that returns a notification message based on the order status. In the index.js
add the following:
接下来,我们要添加一个辅助函数,该函数根据订单状态返回通知消息。 在index.js
添加以下内容:
function getStatusNotificationForOrder(order) { let pizza = order['pizza'] switch (order['status']) { case "Pending": return false; case "Accepted": return `⏳ Your "${pizza['name']}" is being processed.` case "Dispatched": return `?? Your "${order['pizza']['name']}" is on it’s way` case "Delivered": return `? Your "${pizza['name']}" has been delivered. Bon Appetit.` default: return false; } }
Next, in the
PUT /orders/:id
route, add the following code before the return statement:
Next, in the
PUT /orders/:id
route, add the following code before the return statement:
let alertMessage = getStatusNotificationForOrder(order)
if (alertMessage !== false) { pushNotifications.publish([`orders_${user_id}`], { apns: { aps: { alert: { title: "Order Information", body: alertMessage, }, sound: 'default' } } }) .then(response => console.log('Just published:', response.publishId)) .catch(error => console.log('Error:', error)); }
In the code above, we send a push notification to the
**orders_${user_id}**
interest (user_id
is the ID generated and passed to the backend server from the client) whenever the order status is changed. This will be a notification that will be picked up by our client application, since we subscribed for that interest earlier.
In the code above, we send a push notification to the
**orders_${user_id}**
interest ( user_id
is the ID generated and passed to the backend server from the client) whenever the order status is changed. This will be a notification that will be picked up by our client application, since we subscribed for that interest earlier.
Next, in the
POST /orders
route, add the following code before the return statement:
Next, in the
POST /orders
route, add the following code before the return statement:
pushNotifications.publish(['orders'], { apns: { aps: { alert: { title: "⏳ New Order Arrived", body: `An order for ${pizza['name']} has been made.`, }, sound: 'default' } } }) .then(response => console.log('Just published:', response.publishId)) .catch(error => console.log('Error:', error));
In this case, we are sending a push notification to the orders interest. This will be sent to the admin application that is subscribed to the orders interest.
In this case, we are sending a push notification to the orders interest. This will be sent to the admin application that is subscribed to the orders interest.
That’s all there is to adding push notifications using Pusher. Here are screen recordings of our applications in action:
That's all there is to adding push notifications using Pusher. Here are screen recordings of our applications in action:
Conclusion
(Conclusion
)
In this article, we created a basic food delivery system and used that to demonstrate how to use Pusher to send Push Notifications in multiple applications using the same Pusher application. Hopefully you learned how you can use Pusher to simplify the process of sending Push Notifications to your users.
In this article, we created a basic food delivery system and used that to demonstrate how to use Pusher to send Push Notifications in multiple applications using the same Pusher application. Hopefully you learned how you can use Pusher to simplify the process of sending Push Notifications to your users.
This post was first published to Pusher.
This post was first published to Pusher .
翻译自: https://www.freecodecamp.org/news/how-to-build-a-food-delivery-app-with-push-notifications-using-swift-2aa259ffea58/
swift通知栏推送