Nastavak serijala ‘Apple Watch’

Tehnički vodič: Kako komuniciraju iPhone i vaša nova Apple Watch aplikacija?

Sviđa vam se članak?

Preporučite ga vašim prijateljima i kolegama putem društvenih mreža!

U prvom telu tehničkog vodiča koji do detalja opisuje izradu Apple Watch aplikacije, a koji smo objavili u utorak, Aleksandar Vacić, developer i dizajner iz Srbije, otkrio nam je osnove dokumentacije koju je Apple postavio za ravoj aplikacija, te pokrio dizajnerske karakteristike utvrđene striktnim standardima. U drugom delu, Vacić piše o tehničkim karakteristika, te daje korisne primere iz prakse.

drugideo

Ukoliko niste ispratili prvi deo serijala „Tehnički vodič: Kako kreirati Apple Watch aplikaciju“, to možete uraditi na sajtu Netokracija Srbija.

Drugi deo priče je, kako sam već najavio, znatno više tehnički orijentisan. Pretpostavljam da će ovaj deo čitati iOS programeri koji su se zagrejali podešavanjem okruženja i spremni su da se upuste u pisanje konkretnog koda.

Za početak, jedna značajna sitnica vezana za debug. Ako ste pogledali Run 5k preview video vidi se da iOS i Watch app rade u paraleli. Dok tako nešto pravite realna je potreba da debugujete i jednu i drugu aplikaciju. Međutim, kada pokrenete Watch app Build & Run šemu, Xcode će “videti” samo Watch app. Da biste mogli da postavljate breakpoint-e i unutar iOS aplikacije, morate se naknadno prikačiti na odgovarajući proces za iOS app.

Arhitektura aplikacije

U ovom trenutku Watch “app” je u suštini nova vrsta iOS 8 ekstenzije za postojeću iPhone aplikaciju. To je zapravo sjajna stvar jer ako već imate Today widget za svoju iOS app, onda pretpostavljam da već imate rešeno pitanje komunikacije između glavne iOS aplikacije i ekstenzije. Konceptualno, Watch i Today ekstenzije su maltene identične i barem pola posla ste već završili.

Mapa dozvoljenih komunikacija između celina izgleda ovako:

1

Sva zanimljiva komunikacija ide isključivo na relaciji Watch ekstenzija prema iOS aplikaciji i obrnuto. Iz Watch ekstenzije je moguće jednosmerno slati podatke ka Watch app, ali ne i obrnuto.

Watch aplikacija, ono što se vidi na satu, nije ništa drugo do remote display. Vi ama baš ništa ne radite direktno na satu niti sa sata možete pročitati ikakvu informaciju. Watch app takođe nema nikakvu komunikaciju sa Internetom niti (za sada) pristup bilo kakvom senzoru koji sat ima. Oni koji su već videli demoe 3rd party aplikacija će primetiti da tu nešto ne štima – gotovo sve demonstrirane aplikacije imaju mogućnost da izvrše neku akciju, tipa odgovore na SMS, aktiviraju alarm ili na primeru moje app – pokrenu ili pauziraju trening sesiju.

Kako onda to radi?

Interface Builder FTW

Ko do sada nije koristio storyboards u iOS aplikacijama (kao autor ovog članka) prvo što će istražiti jeste: da li se UI može kreirati bez njih? Tough luck – ne može.

Watch app se sastoji isključivo od jednog jedinog storyboard-a i gomile slika. Taj storyboard sadrži jednu ili više ulaznih tačaka, tj. view-ova koji se inicijalno prikazuju kada se storyboard učita.

Evo sada ključnog dela odgovora na prethodno pitanje, ilustrovanog kombinacijom screenshotova iz Xcode-a:

2

View-ovi i kontrole su arhivirani u storyboard-u u Watch app bundle-u koji se pri instalaciji iOS aplikacije iskopira na upareni sat. Međutim, Watch app ne sadrži nikakav kod. Svi kontroleri i sve kontrole se nalaze u watch ekstenziji! Zato sam na početku teksta rekao da je Watch app ništa drugo do remote display, a sav posao se i dalje radi na iPhone-u.

Kada dodirnete dugme prikazano na satu, WatchKit će preko Bluetooth-a okinuti method u Watch ekstenziji. Kada hoćete da na labeli prikazanoj na satu ispišete neki tekst, iz watch ekstenzije izvršite setText: metod i WatchKit će opet preko Bluetooth-a poslati taj tekst na sat i onda će OS na satu to iscrtati.

Ono što nije moguće je da pročitate tekst koji je trenutno prikazan u labeli. Ako vam to treba, morate sami čuvati stanje u watch ekstenziji. Pogledajte WKInterfaceLabel.h header: ne postoji property text, kao za UILabel, već samo method setText:. Zbog toga ne možete da napišete:

Screen Shot 2015-03-19 at 3.45.54 PM

već mora,

Screen Shot 2015-03-19 at 3.46.42 PM

Animirani GIF FTW!

Kada sam prethodno napisao “gomile slika”, to bukvalno mislim – u Run 5k aplikaciji ih imam 400 i kusur. Obzirom da imam jedan tanji i jedan deblji progres bar, za svaki sam pripremio 100 slika – po jedna za svaku stotinku od 0,00 do 1,00. I onda sve to x2, zbog različite rezolucije satova.

Ne postoji pouzdana mogućnost da znate na kojoj rezoluciji sata se aplikacija iscrtava, no to srećom nije ni neophodno: image assets u Interface Builder-u u Xcode 6.2 podržava definisanje slika za obe veličine sata:

3

Par bitnih sitnica ovde, da vam ne pojedu sate debug-ovanja kao meni: WatchKit nikako ne voli da koristite vodeće nule za brojač niti voli prekide u sekvenci. Ja sam u seriji od 100 slika greškom izostavio xx-14 sliku i koju god vrednost progress-a da sam stavio iscrtalo bi se do 13-e i tu stane. Naravno da sam proverio sve druge stvari i čupao kosu satima dok na kraju slučajno nisam uočio u Finder-u da za folder ispiše 99 files umesto 100 i onda skapirao gde je problem. To je taj glamur programerskog posla.

Ako ste pomislili da je kreiranje .xcassets fajla sa slike mukotrpan ručni posao – u pravu ste. Zbog toga sam sebi napravio iOS app koja mi omogućava da kreiram proizvoljne veličine ovih progress barova, a app onda automatski generiše sve potrebne slike za obe veličine sata te ih upakuje u .xcassets format. WatchRingGenerator je open source i možete ga preuzeti sa GitHub-a i izmeniti kako vam već treba.

Nakon ovoga, animiranje progress bara se radi ovako:

Screen Shot 2015-03-19 at 3.08.47 PM

Komunikacija

Craig Hockenberry je odmah po objavljivanju prve bete WatchKita napisao:

Bluetooth Low Energy must be really low power: the design of WKInterfaceObject means it’s going to be ON a lot. Every interaction with the watch has the potential to move actions and data between your pocket and wrist using the radio.

Iz prethodnih primera je jasno zašto to kaže. Bluetooth radio stalno nešto prenosi i troši bateriju. Pritom, činjenica je da taj transfer mora trajati neko vreme. Što više optimizujete tu komunikaciju, to će vaša Watch aplikacije bolje i brže raditi. Tehnički gledano, slike za progress barove sam mogao da kreiram u Watch ekstenziji i da ih ispucavam ka satu koristeći ovaj method:

Screen Shot 2015-03-19 at 3.10.37 PM

Obratite pažnju na razliku: setImageNamed: kaže WatchKit-u da potraži sliku sa tim imenom negde u Watch app bundleu i prikaže je unutar WKInterfaceImage view-a. setImage: šalje binarni fajl slike iz ekstenzije ka satu. Jasno je da nema šanse da postignete 60fps prikaz na satu na taj način jer se u prvom slučaju prenosi 10-ak bajtova teksta dok u drugom treba preneti fajl od oko XX*1200 bajtova, gde je XX vrednost progress bar-a – ako je recimo 0.7, onda treba preneti 70 * 1200 bajtova. U prvom slučaju je to i dalje 10 bajtova + poziv startAnimating… metoda.

Do sada se sigurno već pitate – zar nije bilo jednostavnije da se CoreAnimation omogući na satu umesto ovog zezanja sa slikama? Što reče Craig – BTLE mora da zaista malo troši, toliko da se isplati imati veći storage na satu i drndati BT radio umesto da S1 čip ovo sve iscrtava sam.

Pitaj, da ti odgovorim

WatchKit je doneo jedan lep novi class method kojim ekstenzija može u bilo kom trenutku da kontaktira iOS aplikaciju i da (asinhrono) dobije odgovor:

Screen Shot 2015-03-19 at 3.11.26 PM

Metod radi bez obzira da li je iOS app aktivna u vreme poziva ili ne. Ako nije, iOS će startovati app u background režimu. Sa strane iOS app, UIApplicationDelegate mora implementirati ovaj metod:

Screen Shot 2015-03-19 at 3.12.23 PM

Gde se ovo koristi u praksi?

Opet na primeru: Run 5k može da meri daljinu i brzinu koristeći GPS (Core Location) ali na 5s i novijim iPhone-ima može da iskoristi i pedometer (Core Motion). Aplikacija na satu omogućava da se pokrene nova trening sesija, ali ne zna koje opcije su dozvoljene u samoj iOS aplikaciji. Stoga koristim gore spomenute metode da to saznam:

kod2

Kao što možete videti, imate potpunu slobodu da sami definišete šta su userInfo (pitanje ekstenzija → iOS app) i replyInfo (odgovor iOS app → ekstenzija).

Na osnovu toga mogu prikazati samo GPS ili GPS + Pedometer u meniju koji se prikaže kada korisnik inicira force-touch.

Real-time komunikacija

Spomenuti metod je međutim spor i praktično neupotrebljiv ukoliko Watch app treba kontinuirano osvežavati podacima iz iOS aplikacije.

Kada trening krene, iOS app odbrojava minute, meri pređenu daljinu i trenutnu brzinu i (opciono) ritam srca. Watch app bi sve to trebalo da prikaže u realnom vremenu. Ili barem što realnijem.

Preporučena metoda od strane Apple-ovih dev evangelista je da se iskoristi Darwin Notification Center, tj. socket to socket komunikacija. To je low-level C API i kao takav bi blago rečeno bio mučan za korišćenje.

Na sreću, tu su opet dobri ljudi iOS sveta. Conrad Stoll je kreirao MMWormhole biblioteku koja omogućava jednostavan messaging system između iOS aplikacije i ekstenzija (bilo kojih, ne samo Watch). Interno ova biblioteka se bazira na Darwin NC i koristi se veoma jednostavno, samo ispratite dokumentaciju sa Github stranice.

Handoff

Handoff je lepa i korisna tehnološka novotarija koju su doneli iOS 8 i Yosemite. Apple je uglavnom reklamira i objašnjava koristeći email. Počnete da pišete email na telefonu, pa shvatite da biste radije na Mac-u ili iPad-u i onda spustite iPhone, dodirnete odgovarujuću ikonu na lock screen-u i automagično nastavite kucanje gde ste stali.

U slučaju WatchKit-a, Handoff omogućava transparentni prelaz iz Glance ili Notification ka glavnoj Watch app. Na primer, ako korisnik moje aplikacije već gleda u Glance na kome piše da je sledeća trening sesija Week 2 / Run 3 onda, kada dodirne ekran i time inicira prelazak u aplikacije, Glance tu informaciju prenese u app i time uštedim jedan open ParentApplication…/handleWatchKitExtensionRequest… Bluetooth ciklus.

Takva sitna unapređenja omogućavaju da se Watch app momentalno aktivira i korisniku deluje brža.

Gledano kroz kod, Handoff se trivijalno implementira. Na izvornom delu pozovite ovaj metod kad god se promeni stanje za koje watch app treba znati:

Screen Shot 2015-03-19 at 3.19.33 PM

U ovom slučaju type može biti bilo šta kao i webpageURL, jedino što me zanima je userInfo. Watch app, kao destinacija, implementira ovaj metod:

Screen Shot 2015-03-19 at 3.21.30 PM

i proverava šta je prosleđeno unuta userInfo. Još jedna sitnica koja vas može saplesti: kada se koristi HandoffhandleUserActivity: je mesto na kome se radi inicijalizacija kontrolera, awakeWithContext se uopšte ne poziva.

WatchKit Layout

Na kraju želim da kažem par reči o layout sistemu koji je Apple izabrao za sam UI. Blago rečeno zbunjen sam izborom jer ne liči ni na šta drugo što se koristi – što na iOS-u, što na Mac- u. Nema starijeg springs & struts sistema, niti ima AutoLayout-a.

Najviše podseća na početke HTML-a, kada ste samo mogli da elemente slažete jedan pored drugog ili jedan ispod drugog, pa dokle imate mesta. Kad mesta ponestane, pređe se u novi red i tako iznova.

Nijedna UI kontrola se ne može preklopiti sa nekom drugom, sa izuzetkom WKInterfaceGroup čiji bi pandan u HTML-u bio div element. Tako možete praviti grupu unutar grupe unutar grupe…bez kraja. Unutar grupa, elementi se mogu centrirati (i horizontalno i vertikalno) ili uravnati levo/desno odnosno gore/dole. Grupe i druge kontrole mogu biti fiksirane veličine ili relativnih dimenzija u odnosu na grupu u kojoj se nalaze. I to je to.

Uzeću kao primer osnovni ekran Run 5k Watch app, koji se ispostavio kao ubedljivo najteži za implementaciju.

sat11

Imam prilično kompleksnu grupu labela gde su neke uravnate po levoj ivici, neke po desnoj, neke po gornoj ivici teksta a neke po donjoj. I pri tom oko cele te grupe imam dva centrirana progress bar-a. Ovakav izgled je nemoguće kreirati koristeći WKInterfaceLabel kontrole:

Jedini način da se ovo uradi jeste ovako:

12

Ove dve grupe su zaslužne za iscrtavanje progress barova. WKInterfaceGroup ima metod setBackgroundImageNamed: i ta slika takođe može biti animirana korišćenjem startAnimatingWithImagesInRange:duration:repeatCount: metoda.

Image na dnu hijerarhije je zaslužan za ispis šume teksta u sredini. U Watch ekstenziji generišem sliku koristeći CoreGraphics, tj. UIGraphicsBeginImageContextWithOptions (…) gde bukvalno iscrtam sav taj tekst uz mnogo pipavog računanja veličine teksta sa datim fontom, merenja ascender-a i descender-a za svaki željeni font itd. I onda putem spomenutog setImage: metoda pošaljem tu sliku na sat.

Da postoji AutoLayout i da je moguće slobodno pozicionirati kontrole, sve to bi bilo gotovo za nekih pola sata. Ovako sam potrošio dobrih tri dana dok nisam sve udenuo kako sam zamislio.

Nakon svega, jedino sam se mogao zapitati: aman Apple, zašto? Jedino objašnjenje koje mogu da smislim je da S1 system on a chip u satu prosto ne može u ovom trenutku podržati kompleksnost AutoLayout-a, odnosno da su ti proračuni previše zahtevni za dati hardver. S druge strane, procene su da je S1 u suštini minijaturizovani A5 čip (iz iPad 2, prvi iPad mini)… ko zna.

Kako god bilo, nadam se da će u budućnosti biti nečeg boljeg jer će se inače mnogi Objective-C / Swift developeri upoznati sa lepotama (šic!) ugnježdenih tabela i divova od čega web developeri beže već 20 godina.

Požurite!

Nadam se da vam je ovaj brzi let kroz kreiranje Watch aplikacija bio koristan. Kreiranje je relativno jednostavno i nema mnogo toga da se radi, sem da se igrate sa UI-em i kreirate nešto dopadljivo i korisno.

Run 5k Watch app sam kreirao za nekih desetak dana, gde sam 7 dana potrošio na UI a preostalih par dana na finese komunikacije sa iOS aplikacijom.

Obzirom da će sat krenuti u prodaju 24. aprila, imate šanse da budete prisutni sa svojom aplikacijom prvog dana prodaje. Ako krenete danas.

Savet za kraj: Apple je nedavno objavio svoju listu preporuka za kreiranje ovih aplikacija što prirodno predstavlja obavezno štivo.