Tahun ini, ada beberapa peluang menarik bagi pengembang iOS untuk
Mari kita mulai dengan definisi - widget adalah tampilan yang menunjukkan informasi relevan tanpa meluncurkan aplikasi seluler utama dan selalu ada di ujung jari pengguna. Kemampuan untuk menggunakannya sudah ada di iOS ( Ekstensi Hari Ini ), dimulai dengan iOS 8, tetapi pengalaman pribadi murni saya menggunakannya agak menyedihkan - meskipun desktop khusus dengan widget dialokasikan untuk mereka, saya masih jarang sampai di sana, kebiasaan itu belum berkembang.
Hasilnya, di iOS 14, kami melihat kebangkitan widget, lebih terintegrasi ke dalam ekosistem, dan lebih ramah pengguna (secara teori).
Bekerja dengan kartu loyalitas adalah salah satu fungsi utama aplikasi Dompet kami. Dari waktu ke waktu di review di App Store ada saran dari pengguna tentang kemungkinan menambahkan widget ke Hari Ini. Pengguna, yang berada di kasir, ingin menunjukkan kartu secepat mungkin, mendapatkan diskon dan melarikan diri tentang bisnis mereka, karena penundaan untuk setiap potongan waktu menyebabkan penampilan yang sangat mencela dalam antrian. Dalam kasus kami, widget dapat menyimpan beberapa tindakan pengguna untuk membuka kartu, sehingga pembayaran barang di kasir lebih cepat. Toko juga akan berterima kasih - lebih sedikit antrian di kasir.
Tahun ini, Apple secara tidak terduga merilis rilis iOS segera setelah presentasi, meninggalkan pengembang satu hari untuk menyelesaikan aplikasi mereka di Xcode GM, tetapi kami siap untuk rilis, karena tim iOS kami mulai membuat versi widget mereka sendiri pada versi beta Xcode ... Widget saat ini sedang ditinjau di App Store. Menurut statistik , memperbarui perangkat ke iOS baru cukup cepat ; kemungkinan besar, pengguna akan pergi untuk memeriksa aplikasi mana yang sudah memiliki widget, menemukan milik kami dan dengan senang hati.
Di masa mendatang, kami ingin menambahkan informasi yang lebih relevan - misalnya, saldo, kode batang, pesan terakhir yang belum dibaca dari mitra, dan pemberitahuan (misalnya, pengguna perlu mengambil tindakan - mengonfirmasi atau mengaktifkan kartu). Saat ini, hasilnya terlihat seperti ini:
Menambahkan widget ke proyek
Seperti fitur tambahan serupa lainnya, widget ditambahkan sebagai ekstensi ke proyek utama. Setelah ditambahkan, Xcode dengan ramah menghasilkan kode untuk widget dan kelas inti lainnya. Di sinilah fitur menarik pertama menunggu kami - untuk proyek kami, kode ini tidak dikompilasi, karena di salah satu file, sebuah prefiks secara otomatis dimasukkan ke dalam nama kelas (ya, prefiks Obj-C yang sama!), Tapi tidak di file yang dihasilkan. Seperti yang mereka katakan, bukan dewa yang membakar panci, tampaknya, tim yang berbeda di dalam Apple tidak sepakat di antara mereka sendiri. Mari berharap mereka akan memperbaikinya untuk versi rilis. Untuk menyesuaikan awalan proyek Anda, di File Inspector dari target utama aplikasi, isi bidang Awalan Kelas .
Bagi yang sudah mengikuti berita WWDC, bukan rahasia lagi bahwa penerapan widget hanya bisa dilakukan dengan menggunakan SwiftUI. Hal yang menarik adalah bahwa dengan cara ini Apple memaksakan pembaruan pada teknologinya: meskipun aplikasi utama ditulis menggunakan UIKit, maka, jika Anda mau, hanya SwiftUI. Di sisi lain, ini adalah peluang bagus untuk mencoba kerangka kerja baru untuk menulis fitur, dalam hal ini ini cocok dengan prosesnya - tidak ada perubahan status, tidak ada navigasi, Anda hanya perlu mendeklarasikan UI statis. Artinya, bersama dengan framework baru, batasan baru juga muncul, karena widget lama di Today dapat berisi lebih banyak logika dan animasi.
Salah satu inovasi utama SwiftUI adalah kemampuan untuk melihat pratinjau tanpa meluncurkannya pada simulator atau perangkat ( pratinjau ). Hal yang keren, tetapi, sayangnya, pada proyek besar (di kami - ~ 400K baris kode) ini bekerja sangat lambat bahkan di MacBook teratas, lebih cepat untuk dijalankan di perangkat. Alternatif untuk ini adalah memiliki proyek atau taman bermain kosong untuk pembuatan prototipe cepat.
Debugging juga tersedia dengan skema Xcode khusus. Di simulator, debugging tidak stabil bahkan ke versi Xcode 12 beta 6, jadi lebih baik untuk menyumbangkan salah satu perangkat uji, tingkatkan ke iOS 14 dan uji di atasnya. Bersiaplah bahwa bagian ini tidak akan berfungsi seperti yang diharapkan pada versi rilis.
Antarmuka
Pengguna dapat memilih dari berbagai jenis ( WidgetFamily ) dari tiga ukuran widget - kecil, sedang, besar .
Untuk mendaftar, Anda harus secara eksplisit menentukan yang didukung:
struct CardListWidget: Widget {
public var body: some WidgetConfiguration {
IntentConfiguration(kind: “CardListWidgetKind”,
intent: DynamicMultiSelectionIntent.self,
provider: CardListProvider()) { entry in
CardListEntryView(entry: entry)
}
.configurationDisplayName(" ")
.description(", ")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
Saya dan tim memutuskan untuk tetap menggunakan kecil dan menengah - tampilkan satu kartu favorit untuk widget kecil atau 4 untuk sedang.
Widget ditambahkan ke desktop dari pusat kendali, di mana pengguna memilih jenis yang dia butuhkan:
Sesuaikan warna tombol "Tambahkan widget" menggunakan Assets.xcassets -> AccentColor , nama widget dengan deskripsi juga (contoh kode di atas).
Jika Anda mengalami batasan pada jumlah tampilan yang didukung, Anda dapat memperluasnya menggunakan WidgetBundle :
@main
struct WalletBundle: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
CardListWidget()
MySecondWidget()
}
}
Karena widget menunjukkan snapshot dari beberapa keadaan, satu-satunya kemungkinan interaksi pengguna adalah beralih ke aplikasi utama dengan mengklik beberapa elemen atau seluruh widget. Tidak ada animasi, navigasi, atau transisi ke tampilan lain . Tetapi dimungkinkan untuk menjatuhkan tautan dalam ke aplikasi utama. Dalam hal ini, untuk widget kecil , zona klik adalah seluruh area, dan dalam hal ini kita menggunakan metode widgetURL (_ :) . Untuk menengah dan besar, klik tampilan tersedia , dan struktur Tautan dari SwiftUI akan membantu kami dalam hal ini .
Link(destination: card.url) {
CardView(card: card)
}
Tampilan akhir widget dari dua ukuran ternyata sebagai berikut:
Saat mendesain antarmuka widget, aturan dan persyaratan berikut dapat membantu (menurut pedoman Apple):
- Fokuskan widget pada satu ide dan masalah, jangan mencoba mengulangi semua fungsionalitas aplikasi.
- Menampilkan lebih banyak informasi tergantung pada ukurannya, bukan hanya menskalakan konten.
- Tampilkan informasi dinamis yang dapat berubah sepanjang hari. Ekstrem dalam bentuk informasi yang benar-benar statis dan informasi yang berubah setiap menit tidak diterima.
- Widget harus memberikan informasi yang relevan kepada pengguna, bukan hanya cara lain untuk membuka aplikasi.
Penampilannya telah disesuaikan. Langkah selanjutnya adalah memilih kartu mana yang akan ditampilkan kepada pengguna dan bagaimana caranya. Jelas ada lebih dari empat kartu. Mari pertimbangkan beberapa opsi:
- Izinkan pengguna untuk memilih kartu. Siapa, jika bukan dia, yang tahu kartu mana yang lebih penting!
- Tampilkan peta yang terakhir digunakan.
- Buat algoritme yang lebih cerdas, dengan fokus, misalnya, pada waktu dan hari dalam seminggu dan statistik (jika pengguna pergi ke toko buah di dekat rumah pada hari kerja dan pergi ke hypermarket pada akhir pekan, maka Anda dapat membantu pengguna saat ini dan menunjukkan kartu yang diinginkan)
Sebagai bagian dari prototipe, kami menetapkan opsi pertama untuk pada saat yang sama mencoba kemampuan untuk menyesuaikan parameter secara langsung di widget. Tidak perlu membuat layar khusus di dalam aplikasi. Namun, apakah pengguna, seperti yang mereka katakan, cukup berpengalaman untuk menemukan pengaturan ini?
Pengaturan widget kustom
Pengaturan dibuat menggunakan maksud (halo pengembang Android) - saat membuat widget baru, file maksud secara otomatis ditambahkan ke proyek. Generator kode akan menyiapkan kelas yang diwarisi dari INIntent , yang merupakan bagian dari framework SiriKit . Parameter maksud berisi opsi ajaib "Intent memenuhi syarat untuk widget" . Beberapa jenis parameter tersedia, Anda dapat menyesuaikan subtipe Anda. Karena data dalam kasus kami adalah daftar dinamis, kami juga menyetel item "Opsi disediakan secara dinamis" .
Untuk jenis widget yang berbeda, setel jumlah item maksimum dalam daftar - untuk 1 kecil, untuk sedang 4.
Jenis maksud ini digunakan oleh widget sebagai sumber data.
Selanjutnya, kelas maksud yang dikonfigurasi harus dimasukkan ke dalam konfigurasi IntentConfiguration .
struct CardListWidget: Widget {
public var body: some WidgetConfiguration {
IntentConfiguration(kind: WidgetConstants.widgetKind,
intent: DynamicMultiSelectionIntent.self,
provider: CardListProvider()) { entry in
CardListEntryView(entry: entry)
}
.configurationDisplayName(" ")
.description(", .")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
Jika pengaturan pengguna tidak diperlukan, maka ada alternatif berupa kelas StaticConfiguration, yang bekerja tanpa menentukan maksud.
Judul dan deskripsi dapat diedit pada layar pengaturan.
Nama widget harus muat dalam satu baris, jika tidak maka akan terpotong. Pada saat yang sama, panjang yang diizinkan untuk layar tambah dan pengaturan widget berbeda.
Contoh panjang nama maksimum untuk beberapa perangkat:
iPhone 11 Pro Max
28
21
iPhone 11 Pro
25
19
iPhone SE
24
19
Deskripsi multi-baris. Dalam kasus teks yang sangat panjang di pengaturan, konten dapat digulir. Namun pada layar tambah, pratinjau widget terlebih dahulu dikompresi, lalu sesuatu yang buruk terjadi pada tata letaknya.
Anda juga dapat mengubah warna latar belakang dan nilai-nilai dari WidgetBackground parameter dan AccentColor - secara default mereka sudah dalam Aset . Jika perlu, mereka dapat diganti namanya dalam konfigurasi widget di Build Settings di Asset Catalog Compiler - grup Options di masing-masing bidang Nama Warna Latar Belakang Widget dan Nama Warna Aksen Global .
Beberapa parameter dapat disembunyikan (atau ditampilkan) tergantung pada nilai yang dipilih di parameter lain melalui pengaturan Hubungan .
Perlu diperhatikan bahwa UI untuk mengedit parameter bergantung pada tipenya. Sebagai contoh, jika kita menentukan Boolean , maka kita akan melihat UISwitch , dan jika Integer , maka kita sudah memiliki dua pilihan pilihan: masukan melalui UITextfield atau perubahan langkah demi langkah melalui UIStepper .
Interaksi dengan aplikasi utama.
Bundel telah dikonfigurasi, tetap menentukan dari mana maksud itu sendiri akan mengambil data sebenarnya. Jembatan dengan aplikasi utama dalam hal ini adalah file dalam grup umum ( Grup Aplikasi ). Aplikasi utama menulis, widget membaca.
Metode berikut digunakan untuk mendapatkan URL ke grup umum:
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: “group.ru.yourcompany.yourawesomeapp”)
Kami menyimpan semua kandidat, karena akan digunakan oleh pengguna dalam pengaturan sebagai kamus untuk seleksi.
Selanjutnya, sistem operasi harus mengetahui bahwa datanya telah diperbarui, untuk ini kita sebut:
WidgetCenter.shared.reloadAllTimelines()
// WidgetCenter.shared.reloadTimelines(ofKind: "kind")
Karena pemanggilan metode akan memuat ulang konten widget dan seluruh garis waktu, gunakan saat data benar-benar telah diperbarui agar tidak membebani sistem.
Memperbarui data
Untuk menjaga baterai perangkat pengguna, Apple telah memikirkan mekanisme untuk memperbarui data pada widget menggunakan timeline - mekanisme untuk menghasilkan snapshot . Pengembang tidak secara langsung memperbarui atau mengelola tampilan , tetapi menyediakan jadwal, yang dipandu olehnya, sistem operasi akan memotong snapshot di latar belakang.
Pembaruan berlangsung pada acara berikut:
- Memanggil WidgetCenter.shared.reloadAllTimelines () yang sebelumnya digunakan
- Saat pengguna menambahkan widget ke desktop
- Saat mengedit pengaturan.
Selain itu, pengembang memiliki tiga jenis kebijakan untuk memperbarui garis waktu (TimelineReloadPolicy):
atEnd - update setelah menampilkan snapshot terakhir
tidak pernah - update hanya jika ada panggilan paksa
setelah (_ :) - update setelah jangka waktu tertentu.
Dalam kasus kami, cukup meminta sistem untuk mengambil satu snapshot hingga data kartu diperbarui di aplikasi utama:
struct CardListProvider: IntentTimelineProvider {
public typealias Intent = DynamicMultiSelectionIntent
public typealias Entry = CardListEntry
public func placeholder(in context: Context) -> Self.Entry {
return CardListEntry(date: Date(), cards: testData)
}
public func getSnapshot(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Self.Entry) -> Void) {
let entry = CardListEntry(date: Date(), cards: testData)
completion(entry)
}
public func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void) {
let cards: [WidgetCard]? = configuration.cards?.compactMap { card in
let id = card.identifier
let storedCards = SharedStorage.widgetRepository.restore()
return storedCards.first(where: { widgetCard in widgetCard.id == id })
}
let entry = CardListEntry(date: Date(), cards: cards ?? [])
let timeline = Timeline(entries: [entry], policy: .never)
completion(timeline)
}
}
struct CardListEntry: TimelineEntry {
public let date: Date
public let cards: [WidgetCard]
}
Opsi yang lebih fleksibel akan berguna jika menggunakan algoritme otomatis untuk memilih kartu bergantung pada hari dan waktu.
Secara terpisah, perlu diperhatikan tampilan widget jika berada dalam tumpukan widget ( Smart Stack ). Dalam kasus ini, kita dapat menggunakan dua opsi untuk mengelola prioritas: Saran Siri atau dengan mengatur nilai relevansi TimelineEntry dengan tipe TimelineEntryRelevance . TimelineEntryRelevance berisi dua parameter:
skor - prioritas snapshot saat ini relatif terhadap snapshot lain;
durasi adalah waktu hingga widget tetap relevan dan sistem dapat meletakkannya di posisi teratas dalam tumpukan.
Kedua metode, serta opsi konfigurasi untuk widget, dibahas secara rinci pada sesi WWDC .
Anda juga perlu berbicara tentang bagaimana agar tampilan tanggal dan waktu tetap mutakhir. Karena kami tidak dapat memperbarui konten widget secara teratur, beberapa gaya telah ditambahkan untuk komponen Teks. Saat menggunakan gaya, sistem secara otomatis memperbarui konten komponen saat widget berada di layar. Mungkin di masa mendatang, pendekatan yang sama akan diperluas ke komponen SwiftUI lainnya.
Teks mendukung gaya berikut:
relatif- perbedaan waktu antara tanggal saat ini dan tanggal yang ditentukan. Perlu dicatat di sini: jika tanggal ditentukan di masa depan, maka hitungan mundur dimulai, dan setelah itu tanggal dari saat mencapai nol ditampilkan. Perilaku yang sama akan berlaku untuk dua gaya berikutnya;
offset - mirip dengan yang sebelumnya, tetapi ada indikasi berupa awalan dengan ±;
timer - analog dari timer;
tanggal - tampilan tanggal ;
waktu - tampilan waktu .
Selain itu, dimungkinkan untuk menampilkan interval waktu antara tanggal hanya dengan menentukan interval.
let components = DateComponents(minute: 10, second: 0)
let futureDate = Calendar.current.date(byAdding: components, to: Date())!
VStack {
Text(futureDate, style: .relative)
.multilineTextAlignment(.center)
Text(futureDate, style: .offset)
.multilineTextAlignment(.center)
Text(futureDate, style: .timer)
.multilineTextAlignment(.center)
Text(Date(), style: .date)
.multilineTextAlignment(.center)
Text(Date(), style: .time)
.multilineTextAlignment(.center)
Text(Date() ... futureDate)
.multilineTextAlignment(.center)
}
Pratinjau widget
Ketika ditampilkan untuk pertama kali, widget akan dibuka dalam mode pratinjau, untuk ini kita perlu mengembalikan TimeLineEntry di metode placeholder (in :). Dalam kasus kami, ini terlihat seperti ini:
func placeholder(in context: Context) -> Self.Entry {
return CardListEntry(date: Date(), cards: testData)
}
Setelah itu, pengubah (alasan :) dengan parameter placeholder diterapkan ke tampilan . Dalam hal ini, elemen pada widget ditampilkan buram.
Kita bisa menghapus efek ini dari beberapa elemen dengan menggunakan pengubah unredacted () .
The dokumentasi juga mengatakan bahwa panggilan ke placeholder (di :) metode sinkron dan hasilnya harus kembali secepat mungkin, tidak seperti getSnapshot (di: selesai :) dan getTimeline (di: selesai :)
Elemen pembulatan
Dalam pedoman, disarankan untuk mencocokkan pembulatan elemen dengan pembulatan widget; untuk ini , struktur ContainerRelativeShape ditambahkan di iOS 14 , yang memungkinkan Anda menerapkan bentuk penampung ke tampilan.
.clipShape(ContainerRelativeShape())
Dukungan Objective-C
Jika Anda perlu menambahkan kode Objective-C ke widget (misalnya, kami telah menulis pembuatan gambar barcode di atasnya), semuanya terjadi dengan cara standar dengan menambahkan header penghubung Objective-C. Satu-satunya masalah yang kami hadapi adalah saat membuat, Xcode berhenti melihat file maksud yang dibuat secara otomatis, jadi kami juga menambahkannya ke header penghubung :
#import "DynamicCardSelectionIntent.h"
#import "CardSelectionIntent.h"
#import "DynamicMultiSelectionIntent.h"
Ukuran aplikasi
Pengujian dilakukan pada Xcode 12 beta 6
Tanpa widget: 61.6 MB
Dengan widget: 62.2 MB saya akan
merangkum poin utama yang dibahas dalam artikel:
- Widget adalah cara yang bagus untuk merasakan SwiftUI dalam praktiknya. Tambahkan mereka ke proyek Anda meskipun versi minimum yang didukung lebih rendah dari iOS 14.
- WidgetBundle digunakan untuk meningkatkan jumlah widget yang tersedia, berikut adalah contoh yang bagus tentang berapa banyak widget berbeda yang dimiliki ApolloReddit.
- IntentConfiguration atau StaticConfiguration akan membantu menambahkan pengaturan khusus pada widget itu sendiri, jika pengaturan khusus tidak diperlukan.
- Folder bersama pada sistem file di Grup Aplikasi bersama akan membantu menyinkronkan data dengan aplikasi utama.
- Pengembang dapat memilih dari beberapa kebijakan untuk memperbarui timeline (atEnd, never, after (_ :)).
Dalam hal ini, jalur sulit untuk mengembangkan widget pada versi beta Xcode dapat dianggap lengkap, hanya ada satu langkah sederhana yang tersisa - melalui ulasan di App Store.
PS Versi dengan widget telah melewati moderasi dan sekarang tersedia untuk diunduh di App Store!
Terima kasih telah membaca sampai akhir, saran dan komentar dengan senang hati akan saya berikan. Silakan ikuti survei singkat untuk melihat seberapa populer widget di antara pengguna dan pengembang.