Tuto : Créer un app de météo avec SwiftUI

Par Didier, le

Lorsqu’Apple a annoncé SwiftUI à la WWDC 2019, j’ai tout de suite voulu le prendre en main en regardant les sessions et en suivant des tutoriels sur internet. J’ai eu envie d’aller plus loin qu’une app de démo pour voir ce que SwiftUI pouvait proposer. J’ai donc décidé d’écrire une app de météo en utilisant exclusivement ce framework. Voici comment je m’y suis pris.

Illustration : Tuto : Créer un app de météo avec SwiftUI


Tutoriel rédigé par Benjamin Pisano du studio français Lunabee


Note : pour suivre ce tuto directement sous Xcode, vous pouvez télécharger le projet sur Github.


L’API



Il existe de nombreuses API gratuites qui fournissent des données météo. J’ai choisi Dark Sky pour sa facilité d’utilisation. Vous devrez créer un compte pour générer votre clé d’API et faire fonctionner cette app. Vous pouvez créer votre compte ici et générer votre clé gratuitement.

Illustration : Tuto : Créer un app de météo avec SwiftUI


Pour tester son bon fonctionnement, tapez dans votre terminal :

curl https://api.darksky.net/forecast/your_key/45.572353,5.915807


Cette commande devrait retourner un fichier JSON contenant les données météo de la ville de Chambéry. Vous êtes maintenant prêts à configurer le projet Xcode.

Xcode



Pour utiliser SwiftUI dans votre app, vous devez télécharger Xcode 11 beta sur le site Apple Developper dans la section téléchargements. Prévoyez une bonne connexion internet puisque Xcode 11 pèse un peu plus de 5Go.

Illustration : Tuto : Créer un app de météo avec SwiftUI


Une fois le téléchargement terminé, vous pouvez enfin commencer à créer votre projet avec SwiftUI. Vous êtes impatients ? Moi aussi ! 😃

Ouvrez Xcode, cliquez sur Create New Project (ou allez dans File > New > Project…). Sélectionnez Single View App. Nommez votre projet “Weather” et vérifiez que la case SwiftUI est bien cochée. Cliquez sur next pour créer le projet.

Illustration : Tuto : Créer un app de météo avec SwiftUI

Le panneau de configuration de votre projet.


Nous sommes maintenant prêts à passer au code.

Créer le modèle



Météo



Maintenant que vous connaissez l’url permettant de récupérer les données météo d’une ville, créons notre modèle qui ira récupérer ces données.

Notre modèle devrait ressembler à ceci :

struct Weather: Codable {

var current: HourlyWeather
var hours: Weather.List<HourlyWeather>
var week: Weather.List<DailyWeather>

enum CodingKeys: String, CodingKey {

case current = "currently"
case hours = "hourly"
case week = "daily"

}

}

Weather.swift


extension Weather {

struct List<T: Codable & Identifiable>: Codable {

var list:

enum CodingKeys: String, CodingKey {

case list = "data"

}

}

}

WeatherList.swift


struct DailyWeather: Codable, Identifiable {

var id: Date {
return time
}

var time: Date
var maxTemperature: Double
var minTemperature: Double
var icon: Weather.Icon

enum CodingKeys: String, CodingKey {

case time = "time"
case maxTemperature = "temperatureHigh"
case minTemperature = "temperatureLow"
case icon = "icon"

}

}

DailyWeather.swift


struct HourlyWeather: Codable, Identifiable {

var id: Date {
return time
}

var time: Date
var temperature: Double
var icon: Weather.Icon

}

HourlyWeather.swift


extension Weather {

enum Icon: String, Codable {

case clearDay = "clear-day"
case clearNight = "clear-night"
case rain = "rain"
case snow = "snow"
case sleet = "sleet"
case wind = "wind"
case fog = "fog"
case cloudy = "cloudy"
case partyCloudyDay = "partly-cloudy-day"
case partyCloudyNight = "partly-cloudy-night"

var image: Image {
switch self {
case .clearDay:
return Image(systemName: "sun.max.fill")
case .clearNight:
return Image(systemName: "moon.stars.fill")
case .rain:
return Image(systemName: "cloud.rain.fill")
case .snow:
return Image(systemName: "snow")
case .sleet:
return Image(systemName: "cloud.sleet.fill")
case .wind:
return Image(systemName: "wind")
case .fog:
return Image(systemName: "cloud.fog.fill")
case .cloudy:
return Image(systemName: "cloud.fill")
case .partyCloudyDay:
return Image(systemName: "cloud.sun.fill")
case .partyCloudyNight:
return Image(systemName: "cloud.moon.fill")
}
}

}

}

WeatherIcon.swift


Vous pouvez également créer un WeatherManager qui inclura votre clé d’API Dark Sky.

class WeatherManager {

static let key: String = "" // Enter your darkSky API Key here
static let baseURL: String = "https://api.darksky.net/forecast/\(key)/"

}

WeatherManager.swift


Notre modèle est plutôt simple. Il est conforme au protocole Codable qui nous permet de convertir facilement le JSON retourné par l’API en objet dans notre app. C’est un modèle très classique, c’est pourquoi je ne l’expliquerais pas en détail. Maintenant, créons notre modèle pour chaque ville.

Villes



Le modèle des villes est un peu plus intéressant car il introduit le concept de Binding.

Illustration : Tuto : Cr&eacute;er un app de m&eacute;t&eacute;o avec SwiftUI

Attendez… Quoi ? Le Binding ?


Le concept de binding a été introduit en premier lieu sur Mac OS X dans Interface Builder. Il permettait d’observer une variable et de mettre à jour la partie UI de l’app automatiquement en fonction des changements du modèle. Pas de delegate, pas de completion handler : voici le binding. Dans Mac OS X, il n’était pas très facile d’utiliser le binding. C’était encore pire de debugger avec. Avec le temps, Apple est revenu sur un modèle avec des delegates pour ses composants plutôt que du binding, principalement pour réduire le fossé qui se creusait entre AppKit et UIKit. Ce dernier n’utilisait pas le binding, un concept complètement absent sur iOS.

Illustration : Tuto : Cr&eacute;er un app de m&eacute;t&eacute;o avec SwiftUI

Le binding avec Storyboard sur macOS.


Note : le concept de binding n’est pas spécifique à Swift ou Objective-C.


Aujourd’hui, avec Combine et SwiftUI, Apple remet le binding au goût du jour, d’une manière bien plus élégante. Découvrons-le en créant notre modèle pour les villes.

Tout d’abord, créons notre objet City qui contiendra le nom de la ville et ses données météo. Notre objet devrait ressembler à cela :

import SwiftUI
import Combine

class City: BindableObject {

var didChange = PassthroughSubject<City, Never>()

var name: String
var weather: Weather? {
didSet {
didChange.send(self)
}
}

init(name: String) {
self.name = name
self.getWeather()
}

private func getWeather() {
guard let url = URL(string: WeatherManager.baseURL + "45.572353,5.915807?units=ca&lang=fr") else {
return
}

URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
return
}

do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970

let weatherObject = try decoder.decode(Weather.self, from: data)

DispatchQueue.main.async {
self.weather = weatherObject
}
} catch {
print(error.localizedDescription)
}
}.resume()
}

}

City.swift


Ce code contient plusieurs nouveaux concepts. Pas de panique ! Nous allons les expliquer pas à pas.

Premièrement, vous pouvez voir que l’objet City est conforme au protocole BindableObject qui est un nouveau protocole introduit avec Combine. BindableObject permet à notre objet d’être observé lorsque ses propriétés sont modifiées. Cela requiert un appel fonction dans le setter de nos variables qui ont besoin d’être observées. didChange.send(self) doit être appelé lorsque vous souhaitez notifier que cet objet a subi un changement. Vous pouvez voir ici que nous appelons cette fonction dans le setter de la variable weather. Cela signifie qu’à chaque fois que la variable weather sera modifiée, les observateurs de l’objet City seront notifiés qu’il y a eu un changement sur cet objet et qu’il faut probablement mettre à jour l’interface.

Note : Ici, je n’appelle pas didChange.send(self) dans le setter de la variable name, car on supposera que le nom d’une ville ne change jamais une fois qu’elle est initialisée.


La fonction getWeather() va récupérer les données météo de la ville de manière asynchrone dès que la ville est initialisée. Lorsque les données sont récupérées, elle décode le JSON et modifie la variable weather.

Note importante : d’après la documentation Apple, didChange.send(self) doit être appelée uniquement sur le thread principal. C’est pourquoi j’utilise DispatchQueue.main pour modifier la variable weather puisque URLSession s’exécute en arrière plan.


Vous venez de créer votre objet bindable. Beau travail.

Illustration : Tuto : Cr&eacute;er un app de m&eacute;t&eacute;o avec SwiftUI


Maintenant, créons un CityStore qui contiendra la liste de nos villes. CityStore devra aussi être bindable pour pouvoir observer si une ville a été ajoutée ou supprimée. Mais maintenant que vous avez compris le principe du binding, ça va être super simple non ?

import SwiftUI
import Combine

class CityStore: BindableObject {

let didChange = PassthroughSubject<CityStore, Never>()

var cities: [City] = [City(name: "Chambery")] {
didSet {
didChange.send(self)
}
}

}

CityStore.swift

Illustration : Tuto : Cr&eacute;er un app de m&eacute;t&eacute;o avec SwiftUI


Prenons une petite pause pour résumer ce que nous venons de voir.

Le protocole BindableObject nous permet d’observer les changements d’un objet en appelant didChange.send(self) dans le setter de ses variables.

Nous avons créé un objet City qui est conforme au protocole BindableObject pour observer les changements d’une ville (lorsque les données météo sont récupérées).

Nous avons créé un CityStore qui est conforme au protocole BindableObject pour observer les changements dans notre liste de villes (lorsque l’utilisateur ajoute ou supprime une ville).

L’interface utilisateur



Si vous n’avez jamais vu à quoi ressemble une interface faite avec SwiftUI, je vous invite à regarder les tutoriels d’Apple qui sont excellents pour se familiariser avec la conception d’interfaces.

Créer une interface utilisateur avec SwiftUI est très simple et assez amusant. J’ai toujours utilisé Storyboard pour concevoir mes interfaces, mais SwiftUI a mis la barre très haute. En réduisant significativement le code nécéssaire pour concevoir une interface, SwiftUI permet plus d’itérations, ce que je considère comme un réel avantage pour les développeurs et designers.

Storyboard n’existe plus, puisque SwiftUI génère, en direct, un aperçu de votre app. Si votre code change, l’aperçu se met à jour. Mais encore plus fort : si vous changez l'aperçu, votre code se met à jour aussi. C’est presque magique.

Illustration : Tuto : Cr&eacute;er un app de m&eacute;t&eacute;o avec SwiftUI

Le code et l’aperçu. (Note : vous aurez besoin de macOS 10.15 Catalina pour avoir l’aperçu)


Maintenant, créons notre vue qui contiendra nos villes.

import SwiftUI

struct CityListView : View {

@EnvironmentObject var cityStore: CityStore

@State var isAddingCity: Bool = false
@State private var isEditing: Bool = false

var body: some View {
NavigationView {
List {
Section(header: Text("Your Cities")) {
ForEach(cityStore.cities) { city in
CityRow(city: city)
}
.onDelete(perform: delete)
.onMove(perform: move)
}
}
.navigationBarItems(leading: EditButton(), trailing: addButton)
.navigationBarTitle(Text("Weather"))
}
}

private var addButton: some View {
Button(action: {
self.isAddingCity = true
self.isEditing = false
}) {
Image(systemName: "plus.circle.fill")
.font(.title)
}
.presentation(isAddingCity ? newCityView : nil)
}

private func delete(at offsets: IndexSet) {
for index in offsets {
cityStore.cities.remove(at: index)
}
}

private func move(from source: IndexSet, to destination: Int) {
var removeCities: [City] = []

for index in source {
removeCities.append(cityStore.cities[index])
cityStore.cities.remove(at: index)
}

cityStore.cities.insert(contentsOf: removeCities, at: destination)
}

private var newCityView: Modal {
Modal(NewCityView(isAddingCity: $isAddingCity).environmentObject(cityStore)) {
self.isAddingCity = false
}
}

}

CityListView.swift


Ce code devrait être plutôt facile à comprendre, mais il y quelque nouveaux mots clés. Encore une fois, expliquons-les pas à pas.

Note : Swift 5.1 introduit une nouvelle fonctionnalité appelée Property Wrappers. Je vous recommande cet article qui explique ce concept un peu plus en détail.


var body: some View


Cette variable représente le corps de notre vue. C’est ici que vous allez construire votre interface.

@EnvironmentObject


@EnvironmentObject nous permet d’utiliser une seule instance d’un objet qui sera utilisée globalement dans l’app. Pratique pour des objets comme un utilisateur, qui est utilisé partout dans une app.

@State


@State est un Property Wrapper qui représente un état dans une vue. @State ne doit être utilisé que dans une vue et déclaré private pour éviter de l’utiliser n’importe où. Par exemple, dans notre CityListView, il y a un état qui définit si l’utilisateur est en train d’éditer la liste des villes.

@Binding


@Binding est utilisé pour binder une variable @State. Dans certains cas, vous aurez besoin de passer un état dans une vue enfant de votre hiérarchie. Vous pouvez alors utiliser @Binding dans votre vue enfant et lui passer votre variable @State.

@ObjectBinding


@ObjectBinding est utilisé pour observer les changements d’un objet qui répond au protocole BindableObject que nous avons vu précédemment.

Ces propriétés vont nous permettre de garder notre interface à jour en fonction de notre modèle.

Pour faire de CityListView la première vue qui sera présentée dans notre app, modifiez le fichier SceneDelegate.swift comme ceci :

import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use a UIHostingController as window root view controller
let window = UIWindow(frame: UIScreen.main.bounds)
let cityStore = CityStore()
window.rootViewController = UIHostingController(rootView: CityListView().environmentObject(cityStore))
self.window = window
window.makeKeyAndVisible()
}

}

SceneDelegate.swift


Ici, nous instancions un CityStore et le passons en tant qu’EnvironmentObject à notre CityListView.

Nous pouvons maintenant créer une vue météo pour les villes :

import SwiftUI

struct CityView : View {

@ObjectBinding var city = City(name: "Chambéry")

var body: some View {
List {
Section(header: Text("Now")) {
CityHeaderView(city: city)
}

Section(header: Text("Hourly")) {
CityHourlyView(city: city)
}

Section(header: Text("This week")) {
ForEach(city.weather?.week.list ?? []) { day in
CityDailyView(day: day)
}
}
}
.navigationBarTitle(Text(city.name))
}

}

CityView.swift


Note : Nous utilisons un @ObjectBinding dans notre CityView. Cela permet à l’utilisateur d’accéder à cette vue même si les données météo n’ont pas encore été récupérées. Une fois celle-ci chargées, tous les éléments d’interface qui contiennent une référence à cet objet sera re-rendu à l’écran. Cela ne fonctionne que si votre objet est conforme au protocole BindableObject.


Maintenant, toutes les vues des données météo :

struct CityHeaderView: View {

@ObjectBinding var city: City

var temperature: String {
guard let temperature = city.weather?.current.temperature else {
return "-ºC"
}
return temperature.formattedTemperature
}

var body: some View {
HStack(alignment: .center) {
Spacer()
HStack(alignment: .center, spacing: 16) {
city.weather?.current.icon.image
.font(.largeTitle)
Text(temperature)
.font(.largeTitle)
}
Spacer()
}
.frame(height: 110)
}

}

CityHeaderView.swift


struct CityHourlyView : View {

@ObjectBinding var city: City

private let rowHeight: CGFloat = 110

var body: some View {
ScrollView(alwaysBounceHorizontal: true, showsHorizontalIndicator: false) {
HStack(spacing: 16) {
ForEach(city.weather?.hours.list ?? []) { hour in
VStack(spacing: 16) {
Text(hour.time.formattedHour)
.font(.footnote)
hour.icon.image
.font(.body)
Text(hour.temperature.formattedTemperature)
.font(.headline)
}
}
}
.frame(height: rowHeight)
.padding(.trailing)
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.frame(height: rowHeight)
}

}

CityHourlyView.swift


struct CityDailyView : View {

@State var day: DailyWeather

var body: some View {
ZStack {
HStack(alignment: .center) {
Text(day.time.formattedDay)
Spacer()
HStack(spacing: 16) {
verticalTemperatureView(min: true)
verticalTemperatureView(min: false)
}
}
HStack(alignment: .center) {
Spacer()
day.icon.image
.font(.body)
Spacer()
}
}
}

func verticalTemperatureView(min: Bool) -> some View {
VStack(alignment: .trailing) {
Text(min ? "min" : "max")
.font(.footnote)
.color(.gray)
Text(min ? day.minTemperature.formattedTemperature : day.maxTemperature.formattedTemperature)
.font(.headline)
}
}

}

CityDailyView.swift

Note : La CollectionView n’est pas encore un composant disponible dans SwiftUI. Pour pallier cette absence, nous utilisons ici une ScrollView contenant une HStack pour la vue par heure.


Enfin, nous pouvons créer une vue pour l’ajout d’une nouvelle ville. Construisons notre modèle qui nous suggérera des villes en fonction d’une recherche. Encore une fois, ce modèle sera bindable pour pouvoir observer les résultats de manière asynchrone.

import SwiftUI
import Combine
import MapKit

class CityFinder: NSObject, BindableObject {

var didChange = PassthroughSubject<CityFinder, Never>()

var results: [String] = [] {
didSet {
didChange.send(self)
}
}

private var searcher: MKLocalSearchCompleter

override init() {
results = []
searcher = MKLocalSearchCompleter()
super.init()
searcher.resultTypes = .address
searcher.delegate = self
}

func search(_ text: String) {
searcher.queryFragment = text
}

}

extension CityFinder: MKLocalSearchCompleterDelegate {

func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
results = completer.results.map({ $0.title })
}

}

CityFinder.swift


Nous utiliserons MapKit pour nous suggérer des villes.

Construisons maintenant notre vue modale pour ajouter une ville :

struct NewCityView : View {

@Binding var isAddingCity: Bool
@State private var search: String = ""

@ObjectBinding var cityFinder: CityFinder = CityFinder()
@EnvironmentObject var cityStore: CityStore

var body: some View {
NavigationView {
List {
Section {
TextField($search, placeholder: Text("Search City")) {
self.cityFinder.search(self.search)
}
}

Section {
ForEach(cityFinder.results.identified(by: \.self)) { result in
Button(action: {
self.addCity(from: result)
self.isAddingCity = false
}) {
Text(result)
}
.foregroundColor(.black)
}
}
}
.navigationBarTitle(Text("Add City"))
.navigationBarItems(leading: cancelButton)
.listStyle(.grouped)
}
}

private var cancelButton: some View {
Button(action: {
self.isAddingCity = false
}) {
Text("Cancel")
}
}

private func addCity(from result: String) {
let cityName = result.split(separator: ",").first ?? ""
let city = City(name: String(cityName))
cityStore.cities.append(city)
}

}

NewCityView.swift


Cette vue est intéressante pour deux raisons :

1) Elle nous montre comment afficher une vue modale avec SwiftUI. Pour se faire, vous aurez besoin de passer une variable qui indique si la vue doit être affichée ou non. Actuellement, ce fonctionnement est un peu bugué.

2) @EnvironmentObject nous permet encore une fois d’utiliser une seule instance de notre CityStore à travers notre app avec du binding.

Et voilà ! Vous venez de créer votre première app avec SwiftUI.

Et après ?



Vous pouvez télécharger le projet complet sur mon Github. Vous pouvez aussi me contacter sur Twitter @benjamin_pisano. Et enfin, vous pouvez découvrir Aria sur le Mac AppStore.

Réactions

16 commentaires
Connectez-vous/créez un compte pour réagir à cet article !

Suivez-nous !

Guide d'achat

A voir Mac iOS Autre

MacBook Air

Acheter
Le MacBook Air a été mis à jour en novembre 2020 avec le tout nouveau processeur M1 Made in Cupertino. S'il reprend le design de la génération précédente, la puce permet d'atteindre des performances, selon Apple, 3,5 fois supérieures pour le CPU et 5 fois supérieures pour la partie graphique intégrée. Cette nouvelle version est bien plus intéressante, n'hésitez pas à voir nos tests !

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

MacBook Pro 13"

Acheter
Le MacBook Pro 13" a été mis à jour le 10 novembre 2020 avec le nouveau processeur M1. Il faudra cependant bien réfléchir à un tel achat, la puissance est bien au rendez-vous (regardez nos tests !), mais un modèle 14" est envisagé pour 2021.

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

MacBook Pro 14"

Acheter
Apple dévoile enfin un modèle inédit de 14,2" avec un tout nouveau processeur : le M1 Pro. Mais ce nouveau modèle propose de nombreuses nouveautés ou évolutions : un écran Liquid Retina XDR, un clavier totalement revu avec la disparition de la Touch Bar et le retour des touches de fonction, jusqu'à 32Go de mémoire et du stockage plus généreux (jusqu'à 8To).

Notre conseil d'achat

Acheter maintenant

MacBook Pro 16"

Acheter
Apple dévoile enfin un modèle 16,2" avec deux tout nouveaux processeurs : le M1 Pro ou le M1 Max. Mais ce nouveau modèle propose de nombreuses nouveautés ou évolutions : un écran Liquid Retina XDR, grosse batterie de 100Wh, clavier totalement revu avec la disparition de la Touch Bar et le retour des touches de fonction, jusqu'à 64Go de mémoire et du stockage plus généreux (jusqu'à 8To).

Notre conseil d'achat

Acheter maintenant

iMac

Acheter
L'iMac 21,5" (non Retina) a été mis à jour le 5 juin 2017 avec Thunderbolt 3 et puces Kaby-Lake. On attendait des modèles Coffee Lake (à 6 coeurs) d'ici le courant du printemps/été 2019 mais Apple n'a pas renouvelé cette version.. qui ne vaut plus vraiment le coup en 2019.

Notre conseil d'achat

Acheter maintenant

iMac M1

Acheter
Il aura fallu attendre presque 10 ans pour que les équipes de Jony Ive se décident enfin à offrir une nouvelle robe, à ce qui était autrefois la star de la gamme Apple : l'iMac. Design totalement revu, finesse à tous les étages, alimentation déportée, écran plus grand, Touch ID... Apple a totalement repensé son tout-en-un tout en conservant ce qui en a fait son succès : une machine reste simple à utiliser, peu encombrante et adaptée à de nombreux usages.

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

iMac Pro

Attendre
L'iMac Pro est actuellement le Mac le plus puissant du marché, même s'il a déjà 2 bonnes années d'existence. Apple a rajouté une petite option GPU Vega 64X et 256Go de RAM courant mars 2019, mais rien de bien folichon. N'hésitez pas à consulter nos tests et nos vidéos avant de vous décider ! A noter qu'Apple va sortir un Mac Pro fin 2019, donc si vous n'êtes pas trop pressé, il sera + modulaire...

La note Mac4Ever

3/10

Notre conseil d'achat

Attendre avant d'acheter

Mac Mini

Acheter
Le Mac mini a été mis à jour le 10 novembre 2020 et a reçu le tout nouveau processeur M1. Il est le moins cher de la bande, et sans doute le plus polyvalent avec sa prise HDMI, ses nombreux ports et son format compact. Il s'agit aussi du seul modèle de bureau, conçu pour être raccordé à des écrans externes.

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

Mac Pro

Acheter
Le nouveau Mac Pro est enfin là ! Certes, sont prix est élevé, mais la cible est très claire : les ultra-pro. Vous pouvez acheter sans crainte, (presque) tout est modifiable dans le temps et les modules MPX sont proposés à part par Apple.

Notre conseil d'achat

Acheter maintenant

iPhone SE

Acheter
489€ pour cet iPhone 8 revisité avec puce A13, WiFi 6 et de meilleures capacités photo (mode portrait etc.). C'est le bon-plan de 2020, même si les fans des grands écrans, de Face ID et d'une bonne autonomie préféreront le XR !

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

iPhone 11

Acheter
L'iPhone 11 prends la suite de l'iPhone XR, mais apporte quelques nouveautés intéressantes en photo/vidéo (voir nos tests/vidéos). Il ne sera pas mis à jour avant septembre 2020... avec l'arrivée de la 5G !

Notre conseil d'achat

Acheter maintenant

iPhone 12

Acheter
L'iPhone 12 prends la suite de l'iPhone 11, mais apporte quelques nouveautés intéressantes en photo/vidéo (voir nos tests/vidéos) et surtout la 5G. Il ne sera pas mis à jour avant septembre 2021.

La note Mac4Ever

9/10

Notre conseil d'achat

Acheter maintenant

iPhone 13

Acheter
L'iPhone 13 prends la suite de l'iPhone 12, sans prise de risque, Apple offre quand-même quelques fonctionnalités intéressantes, en terme d'autonomie, de stockage, ou encore d'écran, même si cette année, c'est surtout la version « Pro » qui progresse le plus. Mais l'iPhone est un produit mature, complet et toujours l'une des références du marché.

La note Mac4Ever

9/10

Notre conseil d'achat

Acheter maintenant

iPhone 13 Pro

Acheter
L'iPhone 13 prends la suite de l'iPhone 12, sans prise de risque, Apple offre quand-même quelques fonctionnalités intéressantes, en terme d'autonomie, de stockage, ou encore d'écran, même si cette année, c'est surtout la version « Pro » qui progresse le plus. Mais l'iPhone est un produit mature, complet et toujours l'une des références du marché.

La note Mac4Ever

9/10

Notre conseil d'achat

Acheter maintenant

iPad mini 6

Acheter
Nouveau design (hérité de l'iPad Air 4 et des iPad Pro), nouveaux capteurs photo, nouveau format d'écran, son stéréo, USB C... Les nouveautés ne manquent pas pour cette cuvée 2021, qui tranche avec le moule vieillissant et pourtant réutilisé depuis des années par Apple. Mais à 559€ en 64Go, la tablette vient gentiment taquiner les tarifs de ses aînées, si bien que son positionnement prend tout de suite des allure de petites tablettes professionnelles

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

iPad Air

Acheter
Après avoir fait son grand retour en 2019, l'iPad Air revient avec une 4e déclinaison. On pourra découvrir un processeur A14 Bionic, gravé en 5nm -une première mondiale. L'écran de10.9" offre une résolution de 2 360 x 1 640 pixels à 264 pixels par pouce (ppp). Si tout en bas, on a droit enfin à un connecteur USB-C, avec une charge à 20W, tout en haut se trouve TouchID sur le bouton de démarrage.

Notre conseil d'achat

Acheter maintenant

iPad 9

Acheter
La nouvelle place de l'iPad 9 est plus compliquée à tenir cette année, pour cette tablette d'entrée de gamme, qui évolue timidement. Pour autant, elle ne manque pas d'atouts, comme le prix, l'écran large et lumineux, la prise en charge du Pencil, l'autonomie très correcte, son capteur frontal Ultra Grand-Angle et le chargeur USB C fourni. Le tout dans un design tout de même très daté.

La note Mac4Ever

7/10

Notre conseil d'achat

Acheter maintenant

iPad Pro M1

Acheter
5G, Thunderbolt, puce M1, 16Go de RAM, 2To de stockage, écran HDR... L'iPad Pro M1 n'a rien à envier à un MacBook Pro 13" ou presque ! Hormis macOS, c'est même le Mac qui pourrait être un peu jaloux, face à un écran tactile de cette qualité, de la connectivité au top et un accès aux accessoires les plus rapides du marché.

La note Mac4Ever

9/10

Notre conseil d'achat

Acheter maintenant

Apple Watch 3

Attention
L'Apple Watch Series 3 reste au catalogue (mais peut-être plus pour longtemps ?) et constitue une bonne alternative à la Series 5, même si son écran est un peu plus petit et qu'elle ne propose pas certaines fonctionnalités inédites, comme l'ECG, l'écran allumé en permanence ou la détection de chute, propres à la nouvelle version. Pesez bien le pour et le contre, sachant que ces montres connectées évoluent beaucoup d'une année sur l'autre et son rapidement obsolètes (la première Apple Watch ne prend pas en charge watchOS 5/6 par exemple, alors qu'elle ne date que de 2015)

La note Mac4Ever

4/10

Notre conseil d'achat

Acheter si nécessaire

AirTag

Acheter
L'AirTag est avant-tout destiné à ne pas égarer ses affaires, mais Apple ne veut pas parler de traqueur, respect de la vie privée oblige. Pourtant, ces petites balises bluetooth fonctionnent très bien pour suivre certaines personnes. Avec sa simplicité d'usage, la petite balise bénéficie d'un vrai réseau Bluetooth mondial et d'une localisation proche très efficace.

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

Apple Watch 7

Acheter
la nouvelle Apple Watch Series 7 propose des bordures plus fines, une certification IP6X pour la poussière et WR50 pour l'eau, un écran plus grand et un clavier AZERTY avec QuickPath permettant de glisser le doigt de lettre en lettre. L'écran est 70% plus lumineux en veille, avec une dalle plus résistante, et une autonomie similaire aux générations précédentes. Disponible en 41 et 45 mm, elle n'affiche pas augmentation de tarif.

Notre conseil d'achat

Acheter maintenant

Apple TV HD

Attendre
L'Apple TV 2015 est sortie fin 2015 et Apple le garde au catalogue malgré l'arrivée de la version 4k. Avec en prime, une petite baisse de prix ! Si vous n'avez pas de TV 4k, cela reste une bonne affaire.

Notre conseil d'achat

Attendre avant d'acheter

Apple TV 4K

Acheter
Apple met à jour sa petite boite avec quelques petites nouveautés intéressantes, comme une puce A12, un retour eARC, du HDMI 2.1 ou encore une toute nouvelle télécommande ! Et d'ailleurs, si c'était elle, la star de cette année ?

La note Mac4Ever

8/10

Notre conseil d'achat

Acheter maintenant

iPod touch

Acheter
Après une petite mise à jour en juillet 2015 (même processeur que l'iPhone 6 et un capteur de 8MP), Apple a enfin daigné mettre à jour son baladeur en 2019. Mais les nouveautés sont maigres : processeur A10 (iPhone 7) et 256Go de stockage au maximum. Pour le reste, rien ne change, sauf le prix, qui prend 20€ en entrée de gamme et qui atteint les 469€ pour le haut de gamme ! Assez décevant pour un produit dont le design et les caractéristiques nous ramènent presque 4 ans en arrière...

Notre conseil d'achat

Acheter maintenant
Consulter le guide

A lire

Voir tous les dossiers

Refurb Store

Découvrir toutes les offres

Dernières vidéos

Voir toutes les videos