Reflective UI: Jak si udělat zrcátko z kreditní karty
Jak by řekl Don Salieri, snad každý vývojář si čas od času naprogramuje něco jen tak pro radost. Jestli jste v posledním týdnu sledovali dění na Twitteru, pak vás asi nepřekvapí, že já jsem se tentokrát pustil do tzv. Reflective UI. Vizuálního efektu, který využívá přední nebo zadní kameru k tomu, aby vyplnila část UI prvků kreativním způsobem. Třeba jako zrcátko na kreditní kartě.
Reflective UI: Jak na to?
Ze všeho nejdřív jsem si napsal nějakou základní screenu, která je lehce hodně inspirovaná designem z aplikace Revolut. Díky tomu jsem se mohl soustředit na vývoj a neztrácet čas a energii s designem. Tu těžší část jsem měl ale teprve před sebou – zobrazení výstupu obou kamer najednou. Nikdy jsem to nedělal a tím pádem jsem nevěděl, do čeho jdu, ale říkal jsem si, že když se to povedlo vývojářům z BeReal, tak mně se to musí povést taky. 🤞
Podpora více kamer už od roku 2020
Už v roce 2020 jsme jako iOS vývojáři zažili opravdu hodně změn – jedna z těch pozitivních byla, že Apple spolu s iOS 13 představil podporu pro stream z více kamer najednou. Ještě ten rok vzniklo mnoho aplikací, které tuhle novou funkcionalitu využívaly, ale masové používání obou kamer naráz, začalo až později s příchodem appky BeReal, která byla jednoznačným hitem léta 2022.
Dvakrát googli, jednou piš
Věděl jsem, že pro moji verzi Reflective UI na kreditní kartě budu potřebovat výstup z obou dvou kamer najednou. Co jsem nevěděl (a nezjistil si předem 🤦♂️) bylo, že existuje více způsobů, jak získat výstup z kamery, ale pouze jeden z nich umožňuje získání výstupu z více kamer současně. Jak už asi tušíte, v mojí původní implementaci jsem použil způsob, který mě zavedl do slepé uličky a já kvůli tomu musel přepsat celé ReflectionView, co používám na zobrazení výstupu mnou zvolené kamery.
V původní implementaci jsem pro zprostředkování streamu z kamery použil UIImagePickerController
, který se běžně používá např. pro výběr profilové fotky a pro mé účely se na první pohled zdál dostačující. O pár hodin později jsem však zjistil opak – UIImagePickerController
nepodporuje stream z více kamer najednou. Níže najdete ukázku, jak to nedělat, pokud chcete využívat víc jak jednu kameru naráz.
struct CameraView: UIViewControllerRepresentable {
let camera: UIImagePickerController.CameraDevice
let frame: CGSize
func makeUIViewController(context: Context) -> UIImagePickerController {
let vc = UIImagePickerController()
vc.sourceType = .camera
vc.showsCameraControls = false
vc.cameraDevice = camera
// crop camera output to the frame we want
let cameraTransform = getNewTransform(frame: frame, originTransform: vc.view.transform)
vc.view.transform = cameraTransform
return vc
}
.
.
.
}
Ta jediná správná (a hlavně funkční!) cesta, jak získat výstup obou kamer, je použít AVCaptureMultiCamSession
, který je už podle názvu pro mé účely mnohem vhodnější. Během vývoje jsem ještě zjistil, že pokud chcete výstupy z kamer rozdělit do více Views, je vhodné (vlastně nutné), aby všechny Views spolu sdílely jednu AVCaptureMultiCamSession
, protože v opačném případě poslední nově vytvořená session vždy zrušila spojení předchozích sessions.
struct CameraView: UIViewControllerRepresentable {
let position: AVCaptureDevice.Position
let frame: CGSize
static let session = AVCaptureMultiCamSession()
func makeUIViewController(context: Context) -> UIViewController {
let vc = CameraViewController(session: CameraView.session, position: position, frame: frame)
// crop camera output to the frame we want
let cameraTransform = getNewTransform(frame: frame, originTransform: vc.view.transform)
vc.view.transform = cameraTransform
return vc
}
.
.
.
}
Přední kameru používám pro odraz uživatelova obličeje v jeho kartách a zadní kameru pro dynamicky měnící se barvu textu k jednotlivým transakcím. V mém původním návrhu měla mít každá karta svoji kopii ReflectionView
s přední kamerou – to se mi nakonec nepovedlo docílit, protože se v tomhle návrhu vždy načetl výstup z kamery jen u jedné z karet. Nakonec jsem tedy vytvořil pouze jedno ReflectionView
s přední kamerou, které se v aplikaci nijak nemění, a to jsem překryl řadou karet, které mají poloprůhledné pozadí.
ReflectionView(position: back, frame: frame, blur: 20)
.mask {
VStack {
Transaction(company: "Apple", date: "1. March, 10:02", value: 373.0)
Transaction(company: "Form Factory", date: "25. Fabruary, 17:16", value: 47.73)
Transaction(company: "McDonald's", date: "19. Fabruary, 19:43", value: 7.0)
}
}
Potom, co jsem vyřešil problém se zobrazováním karet, už bylo hračka vytvořit dynamicky měnící se barvy textu pro transakce. Stačilo vzít již funkční ReflectionView
se zadní kamerou a přes něj zobrazit TransactionsView
. Zde si vše sedlo přesně tak, jak jsem to při návrhu zamýšlel.
Posledních pár slov
Ukázali jsme si, jak využít Reflective UI pro zrcátko na kreditní kartě. A na co použijete Reflection UI vy? Dejte vědět a myslete na to, že existuje jen jeden způsob, jak získat výstupy z více kamer současně. 🤠
Source code najdete tady.