Published on

iOS 15'da SwiftUI uchun AsyncImage'dan qanday foydalanish kerak

Authors

Hammaga salom, xush kelibsiz! Men β€” Nik, bu Swiftful Thinking. Ehtimol, payqagandirsiz: men bootcamp kursini birinchi marta yaratganimda, biz iOS 14'da edik. Shundan keyin, bir necha oy oldin, Apple iOS 15'ni chiqardi, va iOS 15'da SwiftUI uchun juda ko'plab ajoyib yangi xususiyatlar mavjud. Bu xususiyatlarni ko'rib chiqish, albatta, juda foydali, shuning uchun men endi qaytib kelib, ushbu bootcamp pleylistlariga iOS 15 xususiyatlarini qamrab oluvchi bir nechta yangi video qo'shaman.

Demak, bu β€” iOS 15 xususiyatlarini ko'rib chiquvchi bir qancha videoning birinchisi bo'ladi, va ushbu videoda biz AsyncImageni ko'rib chiqamiz. Bu, ehtimol, iOS 15'dan kelgan SwiftUI uchun eng mashhur yangi xususiyat, va bu bejiz emas β€” chunki u bizga internetdagi biror manzildan (URL) rasmni atigi bir qator kod bilan yuklab olish imkonini beradi, va rasmni olganimizdan keyin, uni xohlagancha o'lchamlashimiz va shaklga keltirishimiz mumkin. Bularning barchasini bajarish uchun atigi bir qator kod kifoya β€” demak, bu juda kuchli xususiyat, men buni keyingi barcha ilovalarimda ishlataman, va bu, albatta, ko'rib chiqishga arziydi. Keling, Xcode'ga o'tib, ko'rib chiqaylik.


Yangi loyiha fayli yaratish va minimal versiyani o'zgartirish

Men hozir bizning original Swiftful Thinking bootcamp loyihamizdaman β€” iOS 15 uchun yangilanishlar qo'shish uchun shu yerga qaytdim. Navigator'da o'ng tugmani bosib, yangi fayl yaratamiz β€” bu SwiftUI View bo'ladi, va buni AsyncImageBootcamp deb ataymiz. "Create" tugmasini bosamiz (men buni onboarding fayllarimning ostiga joylashtiraman), canvas'ni ulaymiz, va kod yozishni boshlaymiz.

Bu yengil va qiziqarli video bo'ladi, chunki bu β€” Apple iOS 15'da qo'shgan juda sodda va ajoyib xususiyat. Eng sodda AsyncImagedan boshlaylik β€” qavslarni ochib, sodda holatda qoldiramiz, faqat url parametridan foydalanamiz. Albatta, bizga shu yerga qo'yish uchun optional URL kerak β€” shuning uchun faylimizning yuqorisida let url = URL(string: "") deb yozamiz, hozircha bo'sh string bilan, va buni AsyncImagega uzatamiz:

let url = URL(string: "")

var body: some View {
    AsyncImage(url: url)
}

Albatta, bu yerda xatolik chiqadi: AsyncImage faqat iOS 15 uchun mavjud, va bu, albatta, bizga tushunarli. "Fix" tugmasini bosib, har bir faylga shu kabi zaxira (fallback) kodlarni qo'sha boshlashimiz mumkin edi, ammo bizda yana ko'plab iOS 15 videolari bo'lgani uchun, har safar shu kabi tartibsiz kod yozib o'tirishni xohlamayman. Shuning uchun navigator'da loyihamizning o'ziga bosib, minimal deployment (eng kichik qo'llab-quvvatlanadigan versiya)ni 14dan iOS 15ga o'zgartiramiz. (Bu yerda alohida ta'kidlab o'tay: bunday qilishni production (haqiqiy, ishlab chiqarilgan) ilovalarda hozircha tavsiya qilmayman β€” agar bu App Store'da allaqachon mavjud bo'lgan ilova bo'lsa, ehtimol hamon iOS 14'ni ham qo'llab-quvvatlashingiz kerak bo'ladi, ammo biz bu yerda shunchaki o'rganish uchun iOS 15'ga o'tamiz.) Endi xatoligimiz yo'qoladi.


AsyncImage nima uchun kerak

AsyncImagening butun mohiyati shunda: biz biror manzildan (URL) rasmni asinxron (asynchronous) tarzda yuklab olishimiz mumkin β€” bu, asosan, internetdan rasmni qurilmamizga olib kelish, degani. AsyncImage bo'lmasa, buni amalga oshirish uchun haqiqatan ham ko'plab kod yozishimiz kerak bo'lardi. Shuning uchun bu juda qulay xususiyat, ayniqsa siz hozirgina o'rganayotgan bo'lsangiz, va rasm yuklab olish va keshlash bilan bog'liq barcha murakkabliklar bilan shug'ullanishni xohlamasangiz.

Hozircha bizning manzilimizda hech qanday rasm yo'q, shuning uchun AsyncImage shunchaki kulrang fon (konteyner)ni ko'rsatadi β€” bu, rasm hali yuklanmagan paytdagi standart ko'rinish, xolos.


Haqiqiy rasm manzilini ulash

Keling, haqiqiy rasm manzilini topaylik. Internetda bepul rasm API'larini taqdim etadigan ko'plab sayt bor β€” men, masalan, picsum.photos saytidan foydalanaman. Bu saytning API manzili β€” .../photos/200 kabi ko'rinishda, ya'ni 200 piksel o'lchamida tasodifiy rasm qaytaradi. Shu manzilni nusxalab, Xcode'ga qaytib, url o'zgaruvchimizga joylaymiz:

let url = URL(string: "https://picsum.photos/200")

Hozircha hech narsa ko'rinmaydi, chunki AsyncImage shu view ekranga chiqganda bir nechta vazifani bajaradi: avval shu manzildan ma'lumotni yuklab oladi, so'ngra shu ma'lumotni rasmga aylantiradi, va nihoyat shu rasmni konteyner ichiga joylaydi. Shuning uchun hozircha hech narsa ko'rinmaydi, ammo "Play" tugmasini bosganimizda, yuklab olish boshlanadi, va rasm paydo bo'ladi.

Bu rasm API'si har safar boshqacha rasm qaytaradi β€” shuning uchun har safar qaytadan yuklasak, yangi bir rasmni ko'ramiz, bu juda qiziqarli.


Rasmning o'lchami bilan bog'liq muammo

Faqat url parametriga ega bo'lgan shu eng sodda AsyncImagedan foydalansak, rasm o'lchami β€” yuklab olinayotgan haqiqiy rasmning o'lchamiga teng bo'ladi. Demak, biz 200 piksel o'lchamida rasm yuklayotganimiz uchun, uni shu o'lchamda ko'ramiz. Agar 400 piksel o'lchamida rasm yuklasak, ekranda kattaroq rasmni ko'ramiz β€” demak, bu yuklanayotgan haqiqiy rasmning o'lchamiga qarab o'zgaradi.

Albatta, biz bu rasmlarni ilovamizga mos keladigan o'lchamga aylantirishni xohlaymiz β€” chunki rasmni olgandan keyin, ehtimol, uni biroz o'zgartirib, ekranning ma'lum bir joyiga joylashtirishni xohlaymiz. Shuning uchun shunchaki .frame(width: 100, height: 100) qo'shib ko'raylik, kichikroq rasm olish uchun. Ammo agar buni qaytadan ishga tushirsak, ko'ramizki, rasm hamon 400 piksel o'lchamida qolib turibdi β€” frameni qo'shsak ham. Agar preview'ni to'xtatib, framening o'ziga bossak, uning 100x100ga yangilanganini ko'ramiz, ammo rasm yuklanganida hamon kattaroq bo'lib qoladi.

Demak, bu β€” muammo, va buning yechimi β€” AsyncImage uchun boshqa initializerdan foydalanish.


Yechim: content va placeholder bilan AsyncImage

Buning o'rniga, url, content va placeholderga ega bo'lgan variantdan foydalanamiz β€” bu yerda content qismi rasm (Image) bo'ladigan variantni izlaymiz. Enter bosamiz, va urlimizni yana uzatamiz. Content uchun Enter bosamiz β€” bu bizga qaytariladigan rasm bo'ladi (buni keyinroq to'ldiramiz). Va nihoyat, placeholder keladi β€” avval rasm bo'lmaganida o'sha kulrang quti ko'rinardi, bu β€” standart placeholder edi, ammo endi biz o'zimizning maxsus placeholderimizni qo'shishimiz mumkin.

Bu uchun eng keng tarqalgan yechim, ehtimol, shunchaki ProgressView bo'lishi mumkin. Agar preview'ni to'xtatsak, kichik yuklanish indikatorini (siz buni, ehtimol, ko'p marta ko'rgansiz) ko'ramiz, va rasm yuklanganda esa, bizga qaytariladigan rasm beriladi β€” buni esa ekranga qo'yishimiz kerak.

Agar Option tugmasini bosib, shu qaytariladigan rasmning ustiga bossak, uning turi Image ekanligini ko'ramiz β€” bu aynan oldingi videolarda oddiy rasmlar qo'shganimizda ishlatgan bir xil tur. Esda bo'lsa, biz oldingi "rasmlar" videosida rasmlarni resizable qilgan edik, ularning render rejimini, aspect ratio'sini o'zgartirgan, ularni scaledToFit yoki scaledToFill qilgan, va boshqa qiziqarli clipShape kabi narsalarni ham qo'llagan edik. Shu yopiq (closure) ichida olayotgan rasmimiz ham aynan shunday β€” bu shunchaki bir Image, xolos, shuning uchun shu modifikatorlarning barchasini shu yerda ham qo'llashimiz mumkin.

Shuning uchun shu qaytariladigan rasmni resizable qilamiz, kerakli frameni beramiz, va scaledToFit qilamiz:

AsyncImage(url: url) { image in
    image
        .resizable()
        .scaledToFit()
        .frame(width: 100, height: 100)
} placeholder: {
    ProgressView()
}

Live Preview'da "Play" tugmasini bosib ko'raylik β€” endi rasm 400 piksel o'lchamida yuklanayotgan bo'lsa ham, biz uni ekranda aynan kerakli o'lchamga moslashtiramiz β€” bu, rostini aytsam, juda mukammal natija. Bunga burchaklarni yumaloqlashtirish va boshqa narsalarni ham qo'shib, xohlagan ko'rinishga keltirishimiz mumkin.

Bu juda ajoyib, chunki biz haqiqatan ham bir lahzada rasmni yuklab, ekranga qo'yamiz β€” agar AsyncImage bo'lmaganida, buning uchun juda ko'p kod yozishimiz kerak bo'lardi. Shuning uchun bu juda-juda qulay xususiyat β€” agar siz hozirgina boshlayotgan bo'lsangiz va rasm yuklab olish va keshlash bilan shug'ullanishni xohlamasangiz, buni ishlatishni qattiq tavsiya qilaman.


Rasmni moslashtirish va keshlash haqida

Shuni ham ta'kidlab o'tmoqchiman: AsyncImage rasmlarni haqiqatan ham keshlamoqda β€” u standart URLSession keshidan foydalanadi, yuklash davomida. Demak, bu haqida o'ylab yoki tashvishlanib o'tirishimiz shart emas. Albatta, ancha murakkabroq ilovalarda o'z maxsus keshingizni yaratishni xohlashingiz mumkin, ammo bu juda ko'p mehnat talab qiladi, va ko'pchilik ilovalar uchun bundan olinadigan foyda juda kichik bo'lishi mumkin β€” shuning uchun bu juda qulay, va ishlatishni qattiq tavsiya qilaman.


AsyncImagePhase yordamida barcha holatlarni boshqarish

Videoni yakunlashdan oldin, yana bir variantni ko'rsatib o'tmoqchiman β€” bu, menimcha, juda qiziqarli. AsyncImage deb yozib, qavslarni ochamiz, va bu safar url va contentga ega variantdan foydalanamiz, ammo bu safar content qismida rasm emas, balki AsyncImagePhase keladigan variantni izlaymiz.

Enter bosamiz β€” bu yerda url keladi, so'ngra AsyncImagePhase keladi, buni phase deb ataymiz. Agar Option tugmasini bosib, shu phasening ustiga bossak, uning turi AsyncImagePhase ekanligini ko'ramiz β€” hujjatlarga (documentation) qarasak, bu holat uch xil bo'lishi mumkin: empty (bo'sh), success (muvaffaqiyatli β€” bu holatda yana shu rasmni olamiz), va failure (muvaffaqiyatsiz β€” agar yuklab olishga harakat qilib, bu muvaffaqiyatsiz tugasa, biz xatolikni olamiz).

Keling, shu barcha holatlarni ko'rib chiqib, ilovamizga qo'shaylik. Shu yerga switch qo'shamiz, va phase ustida switch qilamiz:

Birinchi holat β€” case .empty: bu, asosan, rasm hali yuklanmagan, deganidir β€” bu bizning placeholder'imiz bilan bir xil, shuning uchun shunchaki ProgressView()ni qo'yamiz.

Keyingi holat β€” case .success(let image): agar muvaffaqiyatli bo'lsa, biz yana rasmni olamiz, va unga avvalgi kabi barcha narsalarni qo'llashimiz kerak β€” shuning uchun avvalgi kodni nusxalab, shu yerga joylaymiz: resizable, scaledToFit, va kerakli frame.

Va so'nggi holat β€” case .failure(let error): agar xatolik bo'lsa, xohlasak, shu xatolikni ekranga chiqarishimiz mumkin, ammo buning o'rniga, rasmni olib bo'lmaganda, shunchaki boshqa bir rasm β€” masalan, "questionmark" (savol belgisi) tizim belgisini qo'yamiz, shriftini esa .headline qilamiz. Albatta, biz bunga odatda duch kelmaymiz, ammo agar rasmni yuklab bo'lmasa, ekranda shunchaki savol belgisi ko'rinadi.

Shu yerda kichik bir ogohlantirish chiqadi: switch barcha ma'lum holatlarni qamrab olgan, ammo AsyncImage kelajakda yana qo'shimcha, hozircha noma'lum holatlarga ega bo'lishi mumkin. Buni hal qilish uchun, yana bir default holat qo'shamiz β€” agar bu empty, success yoki failure bo'lmasa, biz shu yerdagi narsani qo'llaymiz; shuning uchun shu savol belgisini default holat uchun ham qo'llaymiz:

AsyncImage(url: url) { phase in
    switch phase {
    case .empty:
        ProgressView()
    case .success(let image):
        image
            .resizable()
            .scaledToFit()
            .frame(width: 100, height: 100)
    case .failure:
        Image(systemName: "questionmark")
            .font(.headline)
    default:
        Image(systemName: "questionmark")
            .font(.headline)
    }
}

Yana bir bor ishga tushirsak, rasmimiz qaytadan paydo bo'ladi β€” demak, bu, asosan, avvalgi usulimiz bilan deyarli bir xil, faqat biroz ko'proq kod talab qiladi, chunki bu yerda, masalan, muvaffaqiyatsizlik (failure) holatini ham moslashtirishimiz mumkin β€” bu esa, haqiqiy ilovalarda ko'pincha qilishingiz kerak bo'lishi mumkin bo'lgan, ancha realistik bir yondashuv.


Mana shu β€” AsyncImage haqidagi videom. Umid qilamanki, sizlarga yoqdi β€” layk va obuna tugmasini bosishni unutmang. Har doimgidek, men β€” Nik, bu Swiftful Thinking, keyingi videoda ko'rishamiz!

Buy mea coffee