
[ad_1]
Une application de suivi des dépenses pour démontrer le framework AppIntents
La WWDC a toujours été l’un des plus grands événements d’Apple chaque année, alors que la plus grande entreprise technologique au monde annonce ses derniers produits logiciels et matériels dotés d’une technologie de pointe à ses clients, à la presse, au reste du monde et, plus important encore, aux développeurs.
Chaque développeur iOS suit cette journée pour rattraper les derniers produits d’Apple et ajouter de la valeur à leurs applications en intégrant de nouvelles fonctionnalités publiées avec les nouvelles versions des systèmes d’exploitation d’Apple.
Avec la sortie d’iOS 16 et de Xcode 14, nous avons désormais un accès complet aux fonctionnalités d’iOS 16 dans nos iPhones et nos environnements de développement.
Dans cet article, nous allons approfondir l’une de ces fonctionnalités intéressantes introduites par Apple avec iOS 16 pour améliorer et rendre nos applications iOS plus conviviales, appelées App Intents.
Le framework App Intents fournit une méthode de programmation pour rendre le contenu et les fonctionnalités de votre application accessibles aux services système. Ces intentions sont les actions qui peuvent être utilisées dans tout le système. Les intentions d’application rendent les fonctionnalités de votre application disponibles dans plus d’endroits, y compris les raccourcis, Spotlight, les filtres Focus et Siri, ce qui se traduit par un énorme avantage pour vos clients. Ça sonne bien, n’est-ce pas ? Essayons ensuite de comprendre la structure de ces App Intents.
Une intention est un élément unique de fonctionnalité que votre application expose au système. Cette fonctionnalité peut récupérer certaines données de votre application, telles que la recherche de l’heure de la prochaine réunion et l’endroit où se trouve votre commande, et transmettre des données à votre application sans lancer au premier plan, comme l’ajout d’un nouvel événement à votre calendrier. Ces fonctionnalités dépendent totalement de la fonctionnalité de votre application et de votre créativité.
Une intention est principalement composée de trois parties ;
- Métadonnées (les informations sur l’intention telles que le titre affiché dans l’application des raccourcis).
- Paramètres (les entrées que votre intention exige de l’utilisateur à l’aide d’un clavier, d’interactions ou de Siri).
- Méthode Perform (responsable de l’exécution de la fonction principale lorsque l’intention est exécutée).
Les intentions d’application sont aussi simples que d’écrire quelques lignes de code à intégrer dans vos applications. Cependant, si vous souhaitez créer des fonctionnalités plus complexes, le framework vous permet également de le faire. Vous pouvez utiliser toutes les fonctionnalités fournies par Swift pour créer des intentions. Pas besoin de refactoriser vos projets ou d’étapes de génération de code automatique.
Afin de mieux visualiser comment ajouter des intentions d’application à un projet iOS, je vais créer une application de suivi des dépenses appelée HarcaMA. L’application ne sera pas la meilleure au monde en termes de fonctionnalités et d’interface utilisateur, mais elle suffira à expliquer la puissance du framework App Intents.
Dans ce projet, j’utiliserai UIKit avec une approche programmatique lors de la conception de l’interface utilisateur avec SnapKit, mais il convient également de noter que vous pouvez également appliquer des intentions d’application à vos applications SwiftUI.
Notre application aura trois pages. La première est la liste des dépenses que nous avons faites jusqu’à présent, la deuxième consiste à ajouter une nouvelle dépense et la troisième à modifier une dépense actuelle. Nous aurons également la possibilité de supprimer une dépense de l’application. Toutes ces dépenses seront stockées dans Core Data. Je suivrai le modèle de conception MVVM avec délégation pour créer cette application. Alors, commençons par créer notre application à partir de Xcode.
Sélectionnez l’interface du storyboard, rapide comme langue et cochez l’option Utiliser les données de base et enregistrez le projet quelque part dans le stockage.
Comme je l’ai dit, nous n’utiliserons pas de storyboard pour créer des interfaces utilisateur, nous pouvons également supprimer Main.storyboard
fichier depuis le navigateur de projet. Et commencez à configurer notre projet.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = UINavigationController(rootViewController: ViewController())
window?.makeKeyAndVisible()
}
Allez à la SceneDelegate
fichier, et remplacez le contenu de connectionOptions
fonction avec le code ci-dessus.
Alors ouvrez Info.plist
fichier, supprimez la propriété avec la clé UISceneStoryboardFile
et la valeur de Main
.
Enfin, allez dans les paramètres de construction de Target, filtrez le contenu pour Main
et supprimez la valeur de la propriété UIKit Main Storyboard File Base Name.
Nous avons maintenant supprimé le storyboard en toute sécurité et sommes prêts à créer des interfaces utilisateur – après avoir ajouté SnapKit en tant que dépendance à notre projet.
J’utiliserai Swift Package Manager, mais vous pouvez également utiliser CocoaPods ou Carthage. Accédez au menu Fichier et sélectionnez Ajouter un package dans l’option Xcode. Rechercher « https://github.com/SnapKit/SnapKit.git » et appuyez sur le bouton Ajouter un package.
Après un court laps de temps, il vous demandera les produits du package que vous souhaitez inclure dans votre application. Sélectionner uniquement SnapKit est suffisant pour notre objectif.
Maintenant, vous pouvez voir SnapKit dans PackageDependencies
du Navigateur de projet. Continuons avec la création de la structure de dossiers pour notre projet.
Comme le montre l’image ci-dessus, j’ai créé des dossiers pour organiser notre projet avant de commencer le développement. Maintenant, regardons notre première page qui répertorie nos dépenses dans un tableview
. Puisque nous allons travailler avec un UITableView
je pense que c’est une bonne approche de commencer par concevoir les cellules de la vue du tableau.
import UIKit
import SnapKit
class ExpenseListTableViewCell: UITableViewCell {
static let identifier = "ExpenseListTableViewCell"var expense: Expense? {
didSet {
self.configure()
}
}
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.axis = .horizontal
return stackView
}()
private lazy var tailStackView: UIStackView = {
let stackView = UIStackView()
stackView.spacing = 50
return stackView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
return label
}()
private lazy var dateLabel: UILabel = {
let label = UILabel()
label.textAlignment = .right
return label
}()
private lazy var priceLabel: UILabel = {
let label = UILabel()
return label
}()
private func configure(){
self.titleLabel.text = expense?.title.shorted(to: 15)
self.dateLabel.text = expense?.formattedDate
let price = expense?.price ?? 0
self.priceLabel.text = "$ \(price)"
setupConsts()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupConsts(){
tailStackView.addArrangedSubview(dateLabel)
tailStackView.addArrangedSubview(priceLabel)
priceLabel.snp.makeConstraints { $0.width.equalTo(70) }
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(tailStackView)
contentView.addSubview(stackView)
stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
}
}
Collez le code ci-dessus dans un fichier nommé ExpenseListTableViewCell
.rapide. Notez que nous avons une propriété nommée dépense qui représente un Expense
, donc un modèle. Nous exécutons la fonction de configuration lorsque cette dépense est définie pour remplir les vues avec le contenu de cette propriété. Ajoutons également la structure Expense dans le Models
dossier.
struct Expense {
let title: String
let date: Date
let price: Double
var formattedDate: String {
let formatter = DateFormatter()
formatter.dateFormat = "MM-dd HH:mm"
return formatter.string(from: date)
}
}
Notre Expense
Le modèle se compose de trois propriétés stockées et d’une propriété calculée pour l’instant.
Renommez la valeur par défaut ViewController
comme ExpenseListViewController
et commencez à ajouter notre vue de table.
class ExpenseListViewController: UIViewController {let viewModel: ExpenseListViewModel
private lazy var tableView: UITableView = {
let tableView = UITableView()
return tableView
}()init(context: NSManagedObjectContext) {
self.viewModel = ExpenseListViewModel(context: context)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(ExpenseListTableViewCell.self, forCellReuseIdentifier: ExpenseListTableViewCell.identifier)
configureNavigationBar()
}override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
private func configureNavigationBar(){
self.title = "Expenses"
let titleTextAttrs = [NSAttributedString.Key.foregroundColor: UIColor.black]
navigationController?.navigationBar.titleTextAttributes = titleTextAttrs
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapAddExpense))
}
@objc private func didTapAddExpense(){}
extension ExpenseListViewController: UITableViewDelegate, UITableViewDataSource {
}func numberOfSections(in tableView: UITableView) -> Int {
1
}func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
UITableViewCell()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
nil
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
50
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
40
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
}
Collez le code ci-dessus dans le contrôleur de vue. Ici, nous créons la vue du tableau, définissons ses contraintes, enregistrons la cellule et configurons notre barre de navigation en ajoutant un bouton plus pour ajouter de nouvelles dépenses ultérieurement.
Afin de lancer la source de données de vue de table et les fonctions déléguées, nous allons également créer une extension pour notre contrôleur de vue comme ci-dessus.
struct ExpenseListViewModel {var expenseList = [Expense]()
}
Notre modèle de vue est assez basique pour l’instant, mais nous l’améliorerons plus tard. Ajoutons maintenant une instance de modèle de vue dans le contrôleur de vue. Et remplissez certaines des fonctions déléguées de la vue tableau comme ci-dessous.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.expenseList.count
} func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: ExpenseListTableViewCell.identifier, for: indexPath) as? ExpenseListTableViewCell else { return UITableViewCell() }
cell.expense = viewModel.expenseList[indexPath.row]
return cell
} func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.backgroundColor = .systemGray
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.isLayoutMarginsRelativeArrangement = true
let titleLabel = UILabel()
titleLabel.text = "Title"
let tailStackView = UIStackView()
tailStackView.spacing = 100
let dateLabel = UILabel()
dateLabel.text = "Date"
let priceLabel = UILabel()
priceLabel.text = "Price"
stackView.addArrangedSubview(titleLabel)
tailStackView.addArrangedSubview(dateLabel)
tailStackView.addArrangedSubview(priceLabel)
stackView.addArrangedSubview(tailStackView)
return stackView
}

Jusqu’à présent, notre application ressemblera à ceci. Créons maintenant notre page de création et de modification des dépenses.
import UIKitprotocol AddUpdateExpanseDelegate: AnyObject {
func addNewExpanse(title: String, date: Date, price: Double)
func updateExpanse(with expense: Expense)
}
public enum AddEditExpenseMode {
case update
case add
}
class AddUpdateExpenseViewController: UIViewController {
private let mode: AddEditExpenseMode
private let expense: Expense?
weak var delegate: AddUpdateExpanseDelegate?
private lazy var titleTextField: UITextField = {
let textField = UITextField()
textField.attributedPlaceholder = NSAttributedString(string: "Title...", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 0))
textField.leftViewMode = .always
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.lightGray.cgColor
textField.layer.cornerRadius = 12
textField.returnKeyType = .continue
return textField
}()
private lazy var priceTextField: UITextField = {
let textField = UITextField()
textField.attributedPlaceholder = NSAttributedString(string: "Amount...", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 0))
textField.leftViewMode = .always
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.lightGray.cgColor
textField.layer.cornerRadius = 12
textField.keyboardType = .decimalPad
textField.returnKeyType = .done
return textField
}()
private lazy var dateStackView: UIStackView = {
let stackView = UIStackView()
stackView.layer.borderWidth = 1
stackView.layer.borderColor = UIColor.lightGray.cgColor
stackView.layer.cornerRadius = 12
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
stackView.isLayoutMarginsRelativeArrangement = true
return stackView
}()
private lazy var dateLabel: UILabel = {
let label = UILabel()
label.text = "Date"
label.textColor = .lightGray
return label
}()
private lazy var datePicker: UIDatePicker = {
let datePicker = UIDatePicker(frame: .zero)
datePicker.datePickerMode = .dateAndTime
datePicker.timeZone = TimeZone.current
return datePicker
}()
private lazy var addUpdateButton: UIButton = {
let button = UIButton()
button.setTitle(self.mode == .add ? "Add Expense" : "Save Changes", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemPurple
button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.lightGray.cgColor
button.layer.cornerRadius = 8
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
return button
}()
init(mode: AddEditExpenseMode, expense: Expense?) {
self.mode = mode
self.expense = expense
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
configureNavigationBar()
view.backgroundColor = .systemBackground
titleTextField.delegate = self
priceTextField.delegate = self
titleTextField.becomeFirstResponder()
addSubviews()
}
private func configureNavigationBar(){
self.title = self.mode == .add ? "Add Expense" : "Update Expense"
let titleTextAttrs = [NSAttributedString.Key.foregroundColor: UIColor.label]
navigationController?.navigationBar.titleTextAttributes = titleTextAttrs
navigationController?.navigationBar.prefersLargeTitles = true
}
private func addSubviews(){
view.addSubview(titleTextField)
view.addSubview(priceTextField)
dateStackView.addArrangedSubview(dateLabel)
dateStackView.addArrangedSubview(datePicker)
view.addSubview(dateStackView)
view.addSubview(addUpdateButton)
guard let expense = self.expense, self.mode == .update else { return }
fillFields(with: expense)
}
private func fillFields(with expense: Expense){
self.titleTextField.text = expense.title
self.priceTextField.text = String(expense.price)
self.datePicker.date = expense.date
}
override func viewDidLayoutSubviews() {
titleTextField.snp.makeConstraints { make in
make.top.equalToSuperview().offset(200)
make.leading.equalToSuperview().offset(50)
make.trailing.equalToSuperview().inset(50)
make.height.equalTo(52)
}
priceTextField.snp.makeConstraints { make in
make.leading.trailing.equalTo(titleTextField)
make.top.equalTo(titleTextField.snp.bottom).offset(20)
make.height.equalTo(52)
}
dateStackView.snp.makeConstraints { make in
make.leading.trailing.equalTo(titleTextField)
make.top.equalTo(priceTextField.snp.bottom).offset(20)
make.height.equalTo(52)
}
addUpdateButton.snp.makeConstraints { make in
make.trailing.equalTo(titleTextField)
make.top.equalTo(dateStackView.snp.bottom).offset(20)
}
}
@objc private func didTapButton(){
guard let priceText = priceTextField.text,
let price = Double(priceText),
let title = titleTextField.text else { return }
let date = datePicker.date
if self.mode == .add {
delegate?.addNewExpanse(title: title, date: date, price: price)
} else {
let newExpense = Expense(title: title, date: date, price: price)
delegate?.updateExpanse(with: newExpense)
}
self.dismiss(animated: true)
}
}
extension AddUpdateExpenseViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == titleTextField {
priceTextField.becomeFirstResponder()
} else if textField == priceTextField {
view.endEditing(true)
}
return true
}
}
Comme indiqué ci-dessus, nous gérerons les actions d’ajout et de mise à jour dans le même contrôleur de vue, via le paramètre mode. Nous allons utiliser AddUpdateExpanseDelegate
protocole pour déclencher des actions d’ajout et de modification.
Puisque nos interfaces utilisateur sont presque terminées, créons notre modèle d’entité Core Data et ses fichiers de configuration. Ouvert HarcaMA.xcdatamodeld
fichier et appuyez sur pour Add Entity
bouton ci-dessous. Nommez votre entité comme ExpenseItem
et ajoutez des attributs avec les types de données compatibles comme dans l’image ci-dessous. Sélectionnez également l’option Génération de code manuelle dans l’inspecteur de modèle de données.
Pendant que vous sélectionnez datamodel
fichier dans Xcode, accédez à l’éditeur et sélectionnez Create NSManagedObject Subclass
option. Lorsque vous sélectionnez votre modèle d’entité et sélectionnez créer, Xcode créera le CoreDataClass
pour le Entity
et un fichier d’extension incluant ses propriétés.
Après ces étapes, si vous construisez votre projet avec succès, nous pouvons continuer avec la création de notre structure de service.
struct ExpenseService {let context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
}
public func fetchExpenseItems(completion: @escaping (Result<[ExpenseItem], ExpenseServiceErrors>) -> (Void)){
do {
let items = try context.fetch(ExpenseItem.fetchRequest())
completion(.success(items))
} catch {
completion(.failure(.fetchError))
}
}
public func addExpenseItem(title: String, date: Date, price: Double, completion: @escaping (Bool) -> (Void)){
let newItem = ExpenseItem(context: context)
newItem.title = title
newItem.date = date
newItem.price = price
do {
try context.save()
completion(true)
} catch {
completion(false)
}
}
public func updateExpenseItem(with newExpense: Expense, completion: @escaping (Bool) -> (Void)){
guard let prevExpense = context.object(with: newExpense.id) as? ExpenseItem else {
completion(false)
return
}
prevExpense.title = newExpense.title
prevExpense.date = newExpense.date
prevExpense.price = newExpense.price
do {
try context.save()
completion(true)
} catch {
completion(false)
}
}
public func deleteExpenseItem(with id: NSManagedObjectID, completion: @escaping (Bool) -> (Void)){
guard let expense = context.object(with: id) as? ExpenseItem else {
completion(false)
return
}
context.delete(expense)
do {
try context.save()
completion(true)
} catch {
completion(false)
}
}
public func totalExpenseAmount() -> Double? {
do {
let items = try context.fetch(ExpenseItem.fetchRequest())
let total = items.reduce(0) { $0 + $1.price }
return total
} catch {
return nil
}
}
}
public enum ExpenseServiceErrors: Error {
case fetchError
}
Comme indiqué ci-dessus, notre service dispose des opérations CRUD de base sur ExpenseItem
entité. Pour effectuer ces opérations en toute sécurité, nous avons également ajouté un paramètre id de type NSManagedObjectID
dans notre modèle de dépenses, qui est généré automatiquement pour chaque entrée.
Nous devons maintenant modifier notre modèle de vue pour utiliser les fonctions de notre service. Remplacez le contenu du modèle de vue par le code ci-dessous.
class ExpenseListViewModel {var expenseList = [Expense]()
let service: ExpenseService
init(context: NSManagedObjectContext) {
self.service = ExpenseService(context: context)
fetchExpenses(completion: nil)
}
public func fetchExpenses(completion: (() -> Void)?){
service.fetchExpenseItems {[weak self] result in
switch result {
case .success(let expenses):
self?.expenseList = expenses.map { Expense(id: $0.objectID, title: $0.title, date: $0.date, price: $0.price) }
completion?()
case .failure(_):
print("error")
}
}
}
public func addExpense(title: String, date: Date, price: Double, completion: @escaping () -> Void){
service.addExpenseItem(title: title, date: date, price: price) {[weak self] success in
if success {
self?.fetchExpenses(completion: completion)
}
}
}
public func deleteExpense(id: NSManagedObjectID, completion: @escaping () -> Void){
service.deleteExpenseItem(with: id) {[weak self] success in
if success {
self?.fetchExpenses(completion: completion)
}
}
}
public func updateExpense(with newExpense: Expense, completion: @escaping () -> Void){
service.updateExpenseItem(with: newExpense) {[weak self] success in
if success {
self?.fetchExpenses(completion: completion)
}
}
}
}
Ensuite, nous ajouterons une autre extension à ExpenseListViewController et nous nous conformerons à notre protocole. Ajoutez le code ci-dessous dans la classe viewcontroller.
extension ExpanseListViewController: AddUpdateExpanseDelegate {
func updateExpanse(with expense: Expense) {
viewModel.updateExpense(with: expense) {[weak self] in
self?.tableView.reloadData()
}
}func addNewExpanse(title: String, date: Date, price: Double) {
viewModel.addExpense(title: title, date: date, price: price) {[weak self] in
self?.tableView.reloadData()
}
}
}
Modifiez ensuite le contenu de didSelectRowAt
fonctionnent comme ci-dessous.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
showAlertSheet(for: viewModel.expenseList[indexPath.row])}
private func showAlertSheet(for expense: Expense){
let alert = UIAlertController(title: "What to do with this expense?", message: expense.title, preferredStyle: .actionSheet)
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) {[weak self] _ in
guard let strongSelf = self else { return }
strongSelf.viewModel.deleteExpense(id: expense.id) {
strongSelf.tableView.reloadData()
}
}
let editAction = UIAlertAction(title: "Edit", style: .default) { [weak self] _ in
guard let strongSelf = self else { return }
let vc = AddUpdateExpenseViewController(mode: .update, expense: expense)
vc.delegate = strongSelf
strongSelf.present(UINavigationController(rootViewController: vc), animated: true)
}
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel))
alert.addAction(editAction)
alert.addAction(deleteAction)
present(alert, animated: true)
}
Félicitations 🎉. Vous avez maintenant une belle application de suivi des dépenses. Passons maintenant à la partie principale de cet article, qui est AppIntents
.
Dans notre application, nous allons créer deux App Intents différentes.
L’une d’entre elles consistera à ajouter un nouvel enregistrement de dépenses.
L’autre sera d’obtenir les dépenses totales que nous avons faites jusqu’à présent.
Ces fonctionnalités seront accessibles depuis l’application Raccourcis, Spotlight et Siri. Commençons par créer la deuxième intention.
Allez à la Intents
dossier et créez un nouveau fichier Swift appelé ShowTotalExpense.swift
et importer AppIntents
cadre.
Toute intention peut être créée en se conformant à un protocole appelé AppIntent
.
import AppIntentsstruct ShowTotalExpense: AppIntent {
static var title: LocalizedStringResource = ""
func perform() async throws -> some IntentResult {
return .result()
}
}
L’intention peut être créée avec ces deux paramètres qui sont le titre et exécutent la fonction dans sa forme la plus simple. Si votre fonction perform doit s’exécuter sur le thread principal, vous pouvez y parvenir en ajoutant le @MainActor
annotation.
@MainActor
func perform() async throws -> some IntentResult {
print("You know, I am doing something on the main thread...")
return .result()
}
Le paramètre de titre est celui affiché dans l’éditeur de raccourcis. Vous pouvez le définir sur une valeur statique, ou le définir sur une clé de localisation que vous avez enregistrée dans vos fichiers Localizable, et utiliser la valeur de cette clé.
Afin de ne pas garder cet article plus longtemps, nous ne traiterons pas des étapes de localisation mais vous pouvez trouver des tonnes de ressources expliquant comment ajouter le support de la localisation à votre application iOS.
struct ShowTotalExpense: AppIntent {
static var title: LocalizedStringResource = "Show my total expense"func perform() async throws -> some IntentResult & ProvidesDialog {
let total = await getTotalExpanse()
let dialog = IntentDialog(total)
return .result(dialog: dialog)
}
private func getTotalExpanse() async -> LocalizedStringResource {
let service = await ExpenseService(context: (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext)
guard let total = service.totalExpenseAmount() else { return LocalizedStringResource(stringLiteral: "0") }
return LocalizedStringResource(stringLiteral: String(format: "%2.f", total))
}
}
L’aspect final de notre intention sera comme indiqué ci-dessus. Nous exécutons une fonction de service pour obtenir le montant total des dépenses et le retourner. Enfin, nous fournissons une boîte de dialogue à la suite de l’exécution de cette intention.
Si vous exécutez votre application avec ce code, vous ne verrez pas de raccourci créé pour votre application ou ne la trouverez pas sous les projecteurs, car nous devrions également indiquer au système quel AppIntents
sera disponible pour l’utilisateur et configurera ces intentions.
Afin de conserver la modularisation du projet, créons un nouveau fichier Swift et nommons-le comme HarcaMAShortCuts.swift
dans notre dossier Intents. Ce fichier contiendra une structure conforme au protocole AppShortcutsProvider et sera l’entrée principale pour les raccourcis.
struct HarcaMAShortCuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: ShowTotalExpense(),
phrases: [
"What is my total expense in \(.applicationName)?",
"Show my total expense in \(.applicationName)",
"How much did I spend in \(.applicationName)?"
]
)
}
}
La structure a une propriété statique nommée appShortcuts
qui est un tableau de type AppShortcut
. Gardez à l’esprit que nous sommes actuellement limités à 10 raccourcis d’application pour une application iOS. Après avoir défini les paramètres d’intention et de phrases, notre application peut maintenant utiliser notre AppIntent
dans différents endroits.
Vous pouvez ajouter autant de phrases que vous voulez. Ces phrases seront utilisées pour déclencher la méthode d’exécution de l’intention en la tapant dans Spotlight ou en la disant à Siri. Le premier élément du tableau phrases sera celui affiché à l’utilisateur.
Exécutons maintenant notre application et voyons si notre intention d’application fonctionne comme prévu.

Si vous voyez votre AppIntent
et obtenez des résultats réussis, alors soyez fier de vous ! Vous avez maintenant créé un AppIntent
.
Créons notre deuxième intention d’application qui permet à un utilisateur d’ajouter un nouvel élément de dépense. Créer un nouveau fichier nommé AddExpense.swift
dans le dossier Intents et ajoutez-y le code ci-dessous.
struct AddExpense: AppIntent {static var title: LocalizedStringResource = "Add a new expense"
@Parameter(title: "Title")
var title: String
@Parameter(title: "Price")
var price: Double
@Parameter(title: "Date")
var date: Date
func perform() async throws -> some IntentResult & ProvidesDialog {
await addExpense()
let dialog = IntentDialog("Successfully added your \(title) expense!")
return .result(dialog: dialog)
}
private func addExpense() async {
let service = await ExpenseService(context: (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext)
service.addExpenseItem(title: title, date: date, price: price, completion: {_ in})
}
}
Outre le paramètre de titre et la fonction d’exécution, cette intention a également trois paramètres d’entrée. Nous ajoutons le @Parameter
propriété wrapper pour ce type d’entrées lors de l’ajout dans un AppIntent
.
Chaque paramètre d’intention doit être conforme à la AppEnum
protocole avant de l’utiliser dans un AppIntent
. Si vous souhaitez avoir des types de données personnalisés, n’oubliez pas de vous conformer à ce protocole. Étant donné que notre application n’utilise que des types de données de base, nous n’en avons pas besoin car ils sont déjà conformes à ce protocole.
Ici, nous exécutons une autre méthode de service dans la fonction perform après avoir obtenu les entrées de l’utilisateur, que ce soit avec le clavier ou Siri. Montrez ensuite une boîte de dialogue à l’utilisateur. Maintenant, nous devons également ajouter cette intention dans le AppShortcutsProvider
.
struct HarcaMAShortCuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: ShowTotalExpense(),
phrases: [
"What is my total expense in \(.applicationName)?",
"Show my total expense in \(.applicationName)",
"How much did I spend in \(.applicationName)?"
]
)AppShortcut(
intent: AddExpense(),
phrases: [
"Add a new expense to \(.applicationName)",
"Create an expense in \(.applicationName)"
]
)
}
}
Si vous exécutez l’application, vous verrez notre nouvelle intention. Essayons si cela fonctionne comme prévu.

Bravo, ça marche comme un charme !! Nous avons maintenant un autre moyen pour nos utilisateurs d’enregistrer une nouvelle dépense à partir de nombreux endroits sans ouvrir l’application. Croyez-moi, ces fonctionnalités rendent vos applications iOS plus conviviales et les utilisateurs accompliront rapidement les tâches quotidiennes, ce qui se traduira par un énorme avantage pour vous et vos clients.
Jusqu’à présent, nous avons trouvé un moyen d’interagir avec notre application sans se lancer au premier plan. Mais le framework AppIntents permet plus que cela, vous pouvez ouvrir une partie spécifique de votre application en définissant le openAppWhenRun
paramètre à true dans votre AppIntent
struct, créer des résumés de paramètres définissant comment les paramètres d’intention sont renseignés et afficher une interface utilisateur personnalisée telle qu’une vue SwiftUI, créer des entités personnalisées et écrire des requêtes pour les rassembler, renvoyer un résultat à partir d’une intention en ajoutant ReturnsResult
protocole et utiliser le résultat dans une autre intention d’application et ainsi de suite.
Cependant, avant de planifier les raccourcis d’application que vous fournirez pour votre application, pensez également aux restrictions. Vous ne pouvez pas avoir plus de 10 raccourcis d’application pour une application iOS. De plus, si vous souhaitez afficher vos vues personnalisées dans les intentions d’application, ces vues ne peuvent pas inclure d’interactivité ou d’animations. Je vous recommande fortement d’en tenir compte avant de planifier vos intentions d’application. Enfin, nous devrions également réfléchir à la manière dont nos raccourcis d’application seront détectables par les utilisateurs.
Pourtant, le framework AppIntents semble être un moyen formidable d’augmenter l’interaction entre les utilisateurs et les applications et de créer plus de valeur pour nos applications iOS. Réfléchissons à nos raccourcis et voyons les excellentes idées AppIntent créées pour les applications iOS.
Référentiel GitHub :
[ad_2]
Télécharger ici