< Zpět na články

Reflective UI: Jak si udělat zrcátko z kreditní karty

Martin SvobodaMartin Svoboda
14. března 2023

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ě.

ackee cards

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.

Martin Svoboda
Martin Svoboda
iOS DeveloperMartin je součástí iOS týmu, do kterého ho kromě napsání si vlastního pluginu pro Instagram přivedl taky náš předmět na FIT ČVUT. Když nepíše pluginy, rád fotí, vaří nebo zajde do posilovny.

Máte zájem o spolupráci? Pojďme to probrat osobně!

Napište nám >