SlideShare a Scribd company logo
Core UIKit 
drop in replacement for UIKit that runs on OS X 
…a short story 
Daniele Margutti 
Software Architect 
me@danielemargutti.com 
danielemargutti.com
COS’È Una implementazione completa e fedele di UIKit in 
grado di funzionare su OS X. 
PERCHÈ • Apple utilizza già UIKit per il simulatore iOS, ma si tratta 
di una versione privata. 
•Avere il controllo totale dell’ambiente di simulazione ci 
permette di lavorare al nostro IDE senza vincoli derivanti 
dall’uso di tecnologie esterne. 
A CHE PUNTO 
SIAMO 
Abbiamo una architettura di base completa che ci permette 
di simulare senza modifiche app iOS 
QUALCHE 
NUMERO... 
• 8 mesi di sviluppo 
• oltre 350 classi 
• più di 60000 linee di codice 
• vicini al commit #1000 
• ...e un infinito numero di imprecazioni 
Core UIKit
UIKit su iOS 
iOS 
Cocoa Touch UIKit 
Media 
Core Graphics 
OpenGL ES 
Core Animation 
già disponibile su Mac 
Core Services 
Core Data 
Foundation 
già disponibile su Mac 
Core OS 
non rilevante 
INFRASTRUTTURA PER APP 
TOUCH BASED 
• Application Run Loop 
• View Controller per gestire i contenuti e la 
navigazione 
•Controlli UI di base (button,label,table etc.) 
•Gestione e propagazione degli eventi touch/motion 
• Supporto al multitasking 
• Supporto al disegno (Core Graphics) 
• Supporto alle animazioni (Core Animation) 
•Gestione dell’hardware (fotocamera/sensori etc.) 
• Local Push Notification 
• Remote Push Notification 
• Stampa 
• iCloud 
•CoreData
Creare un nuovo simulatore 
•UIView: CALayer come elemento base 
•Architettura del simulatore (con supporto per le istanze multiple) 
•Propagazione degli eventi MacOS al simulatore 
•Adapter: utilizzare oggetti nativi Cocoa all’interno del simulatore
UIView: la scelta 
UIView 
NSView 
CALayer 
(da OS X 10.5) 
CALayer 
Mac OS X 
iOS 
CALayer come unità base 
•Ottenere la stessa architettura di iOS 
•Avere (quasi) gratis la potenza di Core Animation 
•Astrarre da OS X l’ambiente di simulazione
UIView: implementazione 
Init di un UIView 
- (id) initWithFrame:(CGRect) { 
if (self = [super init]) { 
// other init stuff 
viewLayer = [[isa layerClass] alloc] init]; 
viewLayer.delegate = self; 
viewLayer.needsDisplayOnBoundsChange = YES; 
viewLayer.layoutManager = [UILayoutManager mainLayoutManager]; 
// ... again, lots of fancy code 
self.frame = aFrame; 
} 
Ogni UIView si porta dietro un 
CALayer che conterrà la sua 
rappresentazione grafica 
Permette di ricevere l’evento 
layoutSublayersOfLayer 
che useremo per inviare il 
layoutSubviews 
Non farà altro che impostare 
il frame di viewLayer 
- (void) layoutSublayersOfLayer:(CALayer *) aLayer { 
[((UIView*)aLayer.delegate) layoutSubviews] 
} 
Quando il layout manager riceve la richiesta di 
un update a fronte di modifiche del CALayer 
abbiamo la possibilità per il suo UIView di 
eseguire un update del contenuto. 
Init di UILayoutManager associato
UIView: implementazione 
Gestire la gerarchia di UIView 
UIWindow 
UIView 
(di UIViewController) 
UIButton 
UITextField 
CALayer (di UIWindow) 
CALayer 
(dello UIView di UIViewController) 
CALayer (di UIButton) 
CALayer (di UITextField) 
- (void) addSubview:(UIView *) aSubview { 
... 
[aSubview willlMoveFromWindow: aSubview.window toWindow: self.window]; 
[aSubview willMoveToSuperView: self]; 
[aSubview willChangeValueForKey:@”superview”]; 
[aSubview removeFromSuperview]; // rimuoviamo il subview dal suo precedente parent 
subview->viewSuperview = self; // teniamo un riferimento weak al nuovo parent view 
[subviewsArray addObject:self]; // aggiungiamo lo UIView alla nostra gerarchia 
[viewLayer addSublayer: self]; // aggiungiamo il suo layer alla gerarchia dei CALayer 
...
UIView 
UIView: implementazione 
Disegnare in uno UIView 
CALayer 
associato 
! 
-display: 
richiesta dal 
sistema 
(metodi delegate) 
implementa 
-displayLayer:? 
si 
chiamalo 
no 
crea buffer, context 
-drawInContext: 
passali a 
se implementato chiama 
-drawLayer:inContext:? 
chiama 
-drawRect: 
La creazione del buffer e del context è dispendiosa. 
Per evitare che venga fatta anche senza che ci sia reale necessità implementiamo -displayLayer: 
ma mentiamo al CALayer (usando l’override di -respondsToSelector:) e consentendone la 
creazione solo se lo UIView implementa realmente -drawRect:
Architettura del simulatore 
UIKitRuntimePool 
UIKitRuntime UIKitRuntime UIKitRuntime 
UIKitRuntimePool si occupa di allocare e 
mantenere le varie istanze di UIKit 
avviate. 
! 
Ogni oggetto mantiene un riferimento 
al proprio runtime. 
il run di una istanza richiede il device che 
si vuole simulare e un run point 
1. intercetta gli eventi iOS 
(mouse, tastiera, trackpad) 
2. traduce gli eventi Mac in eventi iOS 
3. Li invia lungo il responder chain ome 
accade su iOS 
-runWithDevice:delegate:options: 
UIKitLayer (Flipped NSView) 
eventi cocoa 
mouse/keyboard/touchpad 
UIScreen (CALayer) 
UIWindow (CALayer) 
UIView (CALayer) 
(di UIViewController) 
UIButton 
0,0 
UIStatusBar (CALayer) 
UITextField 
UITableView
Architettura del simulatore 
Intercettare e tradurre gli eventi 
UIKitLayer (NSView) 
mouseDown scrollWheel mouseDragged mouseMoved mouseUp mouseEntered 
Viene identificata la posizione dell’evento rispetto 
allo screen principale e individuato l’eventuale 
UIView sotto il touch. 
Poi a seconda del tipo di evento: 
EVENTO COCOA EQUIVALENTE iOS EVENTI SECONDARI 
NSLeftMouseDown touchesBegan 
• [ALT KEY] viene simulato pinch/rotation: il punto 
del touch diventa il punto intermedio tra due dita 
• [HOLD] viene simulato l’inizio di una gesture 
(gestureTouchesBegin) per le gesture registrate al 
view corrente. 
NSLeftMouseDragged touchesMoved 
Se è iniziata una gesture viene inviato l’evento 
gestureTouchesMoved alle gesture registrate per il 
view corrente. 
NSLeftMouseUp touchesEnded 
Se è in corso una gesture viene inviato l’evento 
gestureTouchesEnded alle gesture registrate per il view 
corrente. 
EVENTI PRINCIPALI
Architettura del simulatore 
Hit test per UIView 
View A 
View B 
View C 
View D 
View E 
hitTest funziona in maniera ricorsiva scendendo nella 
gerarchia dei view “toccati” fino a quando un subview 
risponde all’evento (o si torna al root view). 
!! 
Consideriamo il touch nel “View E”. La catena sarà: 
• Il touch è dentro A quindi controlliamo B e C 
• Il touch non è dentro B, saltiamo 
• Il touch è dentro C, verifichiamo D ed E 
• Il touch non è dentro D ma è dentro E, ritorniamo E
Architettura del simulatore 
Hit test per UIView 
View A 
View B 
View C 
View D 
View E 
- (UIView *) hitTest:(CGPoint) point withEvent:(UIEvent *) event { 
if (hidden || !userInteractionEnabled || alpha < 0.01f) 
return nil; // se il view è disabilitato o nascosto lo ignoriamo 
! 
BOOL isInside = CGRectContainsPoint(self.frame, point); 
for (UIView *subview in [subviewsArray reverseObjectEnumerator]) { 
// convertiamo le coordinate al parent e richiamiamo hitTest 
CGPoint localPoint = [self convertPoint:point fromView:self.superview]; 
if (UIView* hitView = [subview hitTest:localPoint withEvent:event]) 
return hitView; // se cattura l’evento proseguiamo 
} 
// avremo il root view di partenza oppure il subview più interno valido 
return (isInside ? self : nil); 
} 
!
Architettura del simulatore 
Ambiente Multi-UIKit 
E’ possibile supportare più ambienti di UIKit simultaneamente 
semplicemente impostando una macro. 
Un nuovo runtime si occupa di creare: 
• UIApplication 
• UIScreen 
• UIDevice 
1.Tutti gli oggetti* creati all’interno dell’app devono essere 
allocati con un riferimento al loro UIKitRuntime. 
A cascata i figli ereditano il runtime del padre 
(ex. UIWindow lo eredita da UIScreen) 
[[oggetto allocWithRuntime:runtime] init… 
2. Devono essere prodotte delle macro in grado di 
redirezionare le chiamate dei singleton. 
#ifdef UIKitMultipleInstances 
#define UIApplication ((UIApplication*)self.runtime.application; 
#else 
#define UIApplication [UIApplication sharedApplication] 
(*) tutti quegli oggetti che usano riferimenti al singleton o all’ambiente grafico. 
UIKitRuntimePool 
UIKitRuntime 
UIApplication UIK 
UIScreen UIK 
UIWindow UIK 
UIViewController 
UIK 
UIView UIK 
UIButton UIK 
[UIApplication 
sharedApplication] ?
Adapter AppKit 
•Consente di utilizzare oggetti NS* all’interno del simulatore UIKit 
•Necessario per evitare di implementare i controlli più complessi 
• UIWebView contiene un adapter per il WebView di OS X 
• UITextField e UITextView sono implementati come adapter di NSTextField/NSTextView 
Struttura dell’adapter 
UIKitLayer (Flipped NSView) 
UIAppKitAdapter 
(UIView) 
CALayer Adapter 
NSClipView 
NS* Native Object 
aggiunto subview di UIKitLayer 
(dietro tutti gli oggetti) 
E’ uno UIView contenente l’oggetto Cocoa 
incapsulato dentro un NSClipView 
L’adapter acquisisce il layer di NSClipView 
aggiungendolo alla propria gerarchia 
La NSClipView viene posizionata allo stesso 
frame dell’adapter ma all’interno di 
UIKitLayer principale (una NSView)
Adapter AppKit 
Funzionamento dell’adapter 
Evento sopra 
l’adapter 
(es. mouseDown) 
UIKitLayer 
NSClipView 
- (NSView *) hitTest:(NSPoint) aPoint { 
NSView *hitNSView = [super hitTest:aPoint]; 
if (!hitNSView) return nil; 
! 
CGPoint pointInScreen = ... // convertiamo il point nelle coordinate dello UIScreen 
// verifichiamo se il touch è avvenuto su un adapter, se si procediamo col focus 
UIView *touchedUIView = [UIMainScreen touchedViewAt: pointInScreen] 
if ([touchedView isKindOfClass:[UIAppKitAdapter class]]) 
return hitNSView; 
else 
return nil; 
} 
UIKitLayer 
UIAppKitAdapter 
- (void) viewWillDraw { 
[adapter updateLayer]; 
}
Qualche controllo UI rilevante 
•UIScrollView 
•UITableView 
•UINavigationController
UIScrollView 1/3 
• E’ molto simile allo UIView... 
...In verità la gran parte dei metodi utilizza proprietà già esposte dallo UIView! 
• Lo scrolling si basa sull’architettura a layer (“rasterizzazione”+”composizione”) con cui 
iOS/OSX visualizzano elementi sullo schermo 
... proprio come accade con i layer che si usano nei programmi di grafica!
UIScrollView 2/3 
bounds per il raster (0,0,100,80) 
10,20 110,20 
u n d i s e g n o t roppo grande 
80 
100 
Rasterization 
• Si ha un’area di disegno con bounds origin 
tipicamente 0,0. 
• Di ogni view viene prodotta la sua rappresentazione 
(il view non conosce il contesto di destinazione) 
• Quello che eccede l’area di disegno viene escluso dal 
raster finale (...non sempre) 
• Il risultato è la rappresentazione del view 
Composition 
• I view vengono montati per livelli da quello più in 
basso nella gerarchia a quello difronte (frontmost) 
• La proprietà frame.origin di ogni view indica la sua 
posizione nel superview di destinazione. 
0 
LOC 
X= 
COMPISITED Superview.frame.origin.y; frame.origin 
un disegno troppo gr 
20,15 
100 
80 
bounds 0,0 
110 
View.frame.origin.x - 
Superview.frame.origin.x; 
Y= View.frame.origin.y - 
0 
140 
20 
15
UIScrollView 3/3 
Ma che c’entra tutto questo con UIScrollView? 
• Lo scrolling non fa altro che alterare il frame di ogni subview all’interno della scrollview a 
seconda del movimento delle dita sul touchscreen. 
... è un’operazione piuttosto dispendiosa; le scrollview potrebbero avere molte subview 
• Fino ad ora abbiamo avuto bounds origin del superview 0,0. 
Potremmo però modificarlo per ottenere uno spostamento (a livello di rasterizzazione) di tutti i 
view che la compongono, a costo zero! 
X= View.frame.origin.x - 
Superview.frame.origin.x; 
Y= View.frame.origin.y - 
Superview.frame.origin.y; 
COMPISITED LOC 
scrollview bounds {-30,-30,140,110} 
subview frame {20,15} 
un disegno troppo gr 
20-(-30)=50 
15-(-30)=45 
• contentOffset lavora proprio in questo modo: 
! 
- (void)setContentOffset:(CGPoint)offset { 
CGRect bounds = [self bounds]; 
bounds.origin = offset; 
[self setBounds:bounds]; 
} 
bounds -30,-30 
frame.origin 
20,15 
una porzione del disegno si sposta offscreen 
(non viene rasterizzata)
UITableView 1/3 
• E’ tra le classi più usate di iOS (si basa su UIScrollView) 
• Viene utilizzata spesso per mostrare grandi quantità di dati 
• ... è quindi necessario che sia performante e con basso impatto sulla memoria 
Implementazione Lazy 
VENGONO CREATE SOLO LE 
CELLE (UIView) VISIBILI 
NASCOSTI V I S I B I L E 
2 AL MOMENTO DELLO SCROLL, LE CELLE CHE 
3 
1 
FINISCONO OFFSCREEN SONO INSERITE IN 
UNA CACHE PRONTE AD ESSERE RIUTILIZZATE 
DURANTE LO SCROLL NUOVE RIGHE 
DOVRANNO ESSERE VISUALIZZATE... 
SI CERCA NELLA CACHE SE CI SONO 
CELLE DISPONIBILI CON 
L’IDENTIFICATORE RICHIESTO 
DISPONIBILI NON DISPONIBILI 
SI TOLGONO DALLA CACHE 
E VENGONO RESE DISPONIBILI 
SONO ALLOCATE 
NUOVE ISTANZE
UITableView 2/3 
UITableView 
UITableSection #1 
Sec. Header 
Row #1 
Row #2 
Row #3 
Row #n 
Sec. Footer 
Header View 
..... 
UITableSection #1 
Sec. Header 
Row #1 
Row #2 
Row #3 
..... 
Row #n 
Sec. Footer 
Footer View 
UITableSection #1 
Sec. Header 
Row #1 
Row #2 
Row #3 
..... 
Row #n 
Sec. Footer 
PER OGNI SECTION DELLA TABLE VIENE CREATA 
UNA CLASSE CHE NE INCAPSULA LA GEOMETRIA 1 
• frame SEC. HEADER 
• frame PER OGNI ROW 
• frame SEC. FOOTER 
• frame globale del blocco 
(i frame sono rispetto alla table) 
2 OTTENUTA LA GEOMETRIA DEGLI ELEMENTI 
SI RICHIAMA IL METODO CHE SI OCCUPA DI 
VISUALIZZARE GLI ELEMENTI E GESTIRE LA 
CACHE 
- layoutTableStructure 
reloadData: calcolo della geometria
UITableView 3/3 
layoutTableStructure: algoritmo per lo scroll 
• viene chiamata ad ogni cambio di contentOffset (lo scroll) 
• deve perciò essere molto performante 
Row #N-1 
Section #1 
Row #1 
INTERSECA L’AREA 
VISIBILE DELLA TABLE? 
UITableSection #N 
Row #N 
Row #N+1 
NULLA. LA CELLA RIMANE VISIBILE. 
VIENE RIMOSSA E MESSA IN CACHE 
SI 
NO 
1 
2 SI OTTENGONO GLI INDICI DELLE CELLE VISIBILI TRAMITE LA GEOMETRIA. 
PER QUELLE NUOVE SI CHIEDE AL DATASOURCE. 
Row #3 
Row #2 
Section #2 
Row #1 
Row #2 
tableView:cellForRowAtIndexPath: 
IL DATA SOURCE PUO’ CHIEDERE UNA VERSIONE IN 
CACHE CON 
dequeueReusableCellWithIdentifier: 
VIENE RESTITUITA UNA VERSIONE IN CACHE O UNA NUOVA ISTANZA SE 
NON DISPONIBILE. 
2a 
2b 
UITableView 3 
Row #1 
• OGNI NUOVA CELLA VIENE INSERITA TRA QUELLE VISIBILI 
• LA GEOMETRIA CALCOLATA PRIMA CI DA LA SUA 
POSIZIONE FINALE
UINavigationController 
UINavigationBar 
UIScrollView 
UINavigationController 
Struttura di base 
View 
Controller #4 
! 
(not loaded) 
n+2 
BLOCCO 1 BLOCCO 2 BLOCCO 3 
View 
Controller #2 
! 
(current) 
n 
View 
Controller #3 
! 
(push) 
n+1 
View 
Controller #1 
! 
(pop) 
n-1 
View 
Controller #4 
! 
(cached) 
n-2 
stack navigazione fuori dalla cache 
x1 x2 
• Una UIScrollView contiene il view corrente 
• Uno stack (NSArray) mantiene la “storia” del navigation 
• Ad ogni push/pop viene mostrato il view ed eventualmente caricato il view 
controller precedente e successivo (on demand) 
• I controller > n+1 possono essere rimossi dalla cache (e deallocati) 
• I controller < n+1 sono mantenuti per lo stack
Grazie 
dell’attenzione ;-) 
Daniele Margutti 
@danielemargutti daniele@creolabs.com

More Related Content

Similar to Making iOS UIKit Simulator for MacOS X

Sviluppo Di Applicazioni Su I Os
Sviluppo Di Applicazioni Su I OsSviluppo Di Applicazioni Su I Os
Sviluppo Di Applicazioni Su I Os
NoDelay Software
 
Introduzione a WatchKit
Introduzione a WatchKitIntroduzione a WatchKit
Introduzione a WatchKit
Federico Crisafulli
 
Progettazione per Apple Watch - Todi Appy Days 2015
Progettazione per Apple Watch - Todi Appy Days 2015Progettazione per Apple Watch - Todi Appy Days 2015
Progettazione per Apple Watch - Todi Appy Days 2015
Todi Appy Days
 
Wearable Lab: Progettazione per Apple Watch
Wearable Lab: Progettazione per Apple WatchWearable Lab: Progettazione per Apple Watch
Wearable Lab: Progettazione per Apple Watch
Paolo Musolino
 
Xcode - Just do it
Xcode - Just do itXcode - Just do it
Xcode - Just do it
Stefano Zanetti
 
m-v-vm @ UgiAlt.Net
m-v-vm @ UgiAlt.Netm-v-vm @ UgiAlt.Net
m-v-vm @ UgiAlt.Net
Mauro Servienti
 
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Codemotion
 
High specialized vm on open stack cloud
High specialized vm on open stack cloudHigh specialized vm on open stack cloud
High specialized vm on open stack cloud
Gabriele Baldoni
 
Integrazione continua con TFS Build
Integrazione continua con TFS BuildIntegrazione continua con TFS Build
Integrazione continua con TFS Build
Gian Maria Ricci
 
Csp@scuola smarttv corso1
Csp@scuola smarttv corso1Csp@scuola smarttv corso1
Csp@scuola smarttv corso1
CSP Scarl
 
Sviluppo e deployment cross-platform: Dal mobile alla Tv
Sviluppo e deployment cross-platform: Dal mobile alla Tv Sviluppo e deployment cross-platform: Dal mobile alla Tv
Sviluppo e deployment cross-platform: Dal mobile alla Tv
Codemotion
 
Qt Lezione4 Parte2: creare un custom widget plugin per Qt Designer
Qt Lezione4 Parte2: creare un custom widget plugin per Qt DesignerQt Lezione4 Parte2: creare un custom widget plugin per Qt Designer
Qt Lezione4 Parte2: creare un custom widget plugin per Qt Designer
Paolo Sereno
 
Slide Prelaurea. Alessandro Andreosè
Slide Prelaurea. Alessandro AndreosèSlide Prelaurea. Alessandro Andreosè
Slide Prelaurea. Alessandro Andreosè
guesta10af3
 
WPF MVVM Toolkit
WPF MVVM ToolkitWPF MVVM Toolkit
WPF MVVM Toolkit
Alessandro Andreose'
 
Niccolò Becchi: Introduzione a GWT
Niccolò Becchi: Introduzione a GWTNiccolò Becchi: Introduzione a GWT
Niccolò Becchi: Introduzione a GWT
firenze-gtug
 
Modi innovativi per costruire App
Modi innovativi per costruire AppModi innovativi per costruire App
Modi innovativi per costruire App
Commit University
 
BBox e vaadin7
BBox e vaadin7BBox e vaadin7
BBox e vaadin7
Federico Russo
 

Similar to Making iOS UIKit Simulator for MacOS X (20)

Programming iOS lezione 1
Programming iOS lezione 1Programming iOS lezione 1
Programming iOS lezione 1
 
Programming iOS lezione 2
Programming iOS lezione 2Programming iOS lezione 2
Programming iOS lezione 2
 
Sviluppo Di Applicazioni Su I Os
Sviluppo Di Applicazioni Su I OsSviluppo Di Applicazioni Su I Os
Sviluppo Di Applicazioni Su I Os
 
Introduzione a WatchKit
Introduzione a WatchKitIntroduzione a WatchKit
Introduzione a WatchKit
 
Progettazione per Apple Watch - Todi Appy Days 2015
Progettazione per Apple Watch - Todi Appy Days 2015Progettazione per Apple Watch - Todi Appy Days 2015
Progettazione per Apple Watch - Todi Appy Days 2015
 
Wearable Lab: Progettazione per Apple Watch
Wearable Lab: Progettazione per Apple WatchWearable Lab: Progettazione per Apple Watch
Wearable Lab: Progettazione per Apple Watch
 
Xcode - Just do it
Xcode - Just do itXcode - Just do it
Xcode - Just do it
 
m-v-vm @ UgiAlt.Net
m-v-vm @ UgiAlt.Netm-v-vm @ UgiAlt.Net
m-v-vm @ UgiAlt.Net
 
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
Andrea Ceroni - Alexa, please deploy my Azure architecture - Codemotion Rome ...
 
High specialized vm on open stack cloud
High specialized vm on open stack cloudHigh specialized vm on open stack cloud
High specialized vm on open stack cloud
 
Integrazione continua con TFS Build
Integrazione continua con TFS BuildIntegrazione continua con TFS Build
Integrazione continua con TFS Build
 
Csp@scuola smarttv corso1
Csp@scuola smarttv corso1Csp@scuola smarttv corso1
Csp@scuola smarttv corso1
 
Sviluppo e deployment cross-platform: Dal mobile alla Tv
Sviluppo e deployment cross-platform: Dal mobile alla Tv Sviluppo e deployment cross-platform: Dal mobile alla Tv
Sviluppo e deployment cross-platform: Dal mobile alla Tv
 
Programming iOS lezione 4
Programming iOS lezione 4Programming iOS lezione 4
Programming iOS lezione 4
 
Qt Lezione4 Parte2: creare un custom widget plugin per Qt Designer
Qt Lezione4 Parte2: creare un custom widget plugin per Qt DesignerQt Lezione4 Parte2: creare un custom widget plugin per Qt Designer
Qt Lezione4 Parte2: creare un custom widget plugin per Qt Designer
 
Slide Prelaurea. Alessandro Andreosè
Slide Prelaurea. Alessandro AndreosèSlide Prelaurea. Alessandro Andreosè
Slide Prelaurea. Alessandro Andreosè
 
WPF MVVM Toolkit
WPF MVVM ToolkitWPF MVVM Toolkit
WPF MVVM Toolkit
 
Niccolò Becchi: Introduzione a GWT
Niccolò Becchi: Introduzione a GWTNiccolò Becchi: Introduzione a GWT
Niccolò Becchi: Introduzione a GWT
 
Modi innovativi per costruire App
Modi innovativi per costruire AppModi innovativi per costruire App
Modi innovativi per costruire App
 
BBox e vaadin7
BBox e vaadin7BBox e vaadin7
BBox e vaadin7
 

Making iOS UIKit Simulator for MacOS X

  • 1. Core UIKit drop in replacement for UIKit that runs on OS X …a short story Daniele Margutti Software Architect me@danielemargutti.com danielemargutti.com
  • 2. COS’È Una implementazione completa e fedele di UIKit in grado di funzionare su OS X. PERCHÈ • Apple utilizza già UIKit per il simulatore iOS, ma si tratta di una versione privata. •Avere il controllo totale dell’ambiente di simulazione ci permette di lavorare al nostro IDE senza vincoli derivanti dall’uso di tecnologie esterne. A CHE PUNTO SIAMO Abbiamo una architettura di base completa che ci permette di simulare senza modifiche app iOS QUALCHE NUMERO... • 8 mesi di sviluppo • oltre 350 classi • più di 60000 linee di codice • vicini al commit #1000 • ...e un infinito numero di imprecazioni Core UIKit
  • 3. UIKit su iOS iOS Cocoa Touch UIKit Media Core Graphics OpenGL ES Core Animation già disponibile su Mac Core Services Core Data Foundation già disponibile su Mac Core OS non rilevante INFRASTRUTTURA PER APP TOUCH BASED • Application Run Loop • View Controller per gestire i contenuti e la navigazione •Controlli UI di base (button,label,table etc.) •Gestione e propagazione degli eventi touch/motion • Supporto al multitasking • Supporto al disegno (Core Graphics) • Supporto alle animazioni (Core Animation) •Gestione dell’hardware (fotocamera/sensori etc.) • Local Push Notification • Remote Push Notification • Stampa • iCloud •CoreData
  • 4. Creare un nuovo simulatore •UIView: CALayer come elemento base •Architettura del simulatore (con supporto per le istanze multiple) •Propagazione degli eventi MacOS al simulatore •Adapter: utilizzare oggetti nativi Cocoa all’interno del simulatore
  • 5. UIView: la scelta UIView NSView CALayer (da OS X 10.5) CALayer Mac OS X iOS CALayer come unità base •Ottenere la stessa architettura di iOS •Avere (quasi) gratis la potenza di Core Animation •Astrarre da OS X l’ambiente di simulazione
  • 6. UIView: implementazione Init di un UIView - (id) initWithFrame:(CGRect) { if (self = [super init]) { // other init stuff viewLayer = [[isa layerClass] alloc] init]; viewLayer.delegate = self; viewLayer.needsDisplayOnBoundsChange = YES; viewLayer.layoutManager = [UILayoutManager mainLayoutManager]; // ... again, lots of fancy code self.frame = aFrame; } Ogni UIView si porta dietro un CALayer che conterrà la sua rappresentazione grafica Permette di ricevere l’evento layoutSublayersOfLayer che useremo per inviare il layoutSubviews Non farà altro che impostare il frame di viewLayer - (void) layoutSublayersOfLayer:(CALayer *) aLayer { [((UIView*)aLayer.delegate) layoutSubviews] } Quando il layout manager riceve la richiesta di un update a fronte di modifiche del CALayer abbiamo la possibilità per il suo UIView di eseguire un update del contenuto. Init di UILayoutManager associato
  • 7. UIView: implementazione Gestire la gerarchia di UIView UIWindow UIView (di UIViewController) UIButton UITextField CALayer (di UIWindow) CALayer (dello UIView di UIViewController) CALayer (di UIButton) CALayer (di UITextField) - (void) addSubview:(UIView *) aSubview { ... [aSubview willlMoveFromWindow: aSubview.window toWindow: self.window]; [aSubview willMoveToSuperView: self]; [aSubview willChangeValueForKey:@”superview”]; [aSubview removeFromSuperview]; // rimuoviamo il subview dal suo precedente parent subview->viewSuperview = self; // teniamo un riferimento weak al nuovo parent view [subviewsArray addObject:self]; // aggiungiamo lo UIView alla nostra gerarchia [viewLayer addSublayer: self]; // aggiungiamo il suo layer alla gerarchia dei CALayer ...
  • 8. UIView UIView: implementazione Disegnare in uno UIView CALayer associato ! -display: richiesta dal sistema (metodi delegate) implementa -displayLayer:? si chiamalo no crea buffer, context -drawInContext: passali a se implementato chiama -drawLayer:inContext:? chiama -drawRect: La creazione del buffer e del context è dispendiosa. Per evitare che venga fatta anche senza che ci sia reale necessità implementiamo -displayLayer: ma mentiamo al CALayer (usando l’override di -respondsToSelector:) e consentendone la creazione solo se lo UIView implementa realmente -drawRect:
  • 9. Architettura del simulatore UIKitRuntimePool UIKitRuntime UIKitRuntime UIKitRuntime UIKitRuntimePool si occupa di allocare e mantenere le varie istanze di UIKit avviate. ! Ogni oggetto mantiene un riferimento al proprio runtime. il run di una istanza richiede il device che si vuole simulare e un run point 1. intercetta gli eventi iOS (mouse, tastiera, trackpad) 2. traduce gli eventi Mac in eventi iOS 3. Li invia lungo il responder chain ome accade su iOS -runWithDevice:delegate:options: UIKitLayer (Flipped NSView) eventi cocoa mouse/keyboard/touchpad UIScreen (CALayer) UIWindow (CALayer) UIView (CALayer) (di UIViewController) UIButton 0,0 UIStatusBar (CALayer) UITextField UITableView
  • 10. Architettura del simulatore Intercettare e tradurre gli eventi UIKitLayer (NSView) mouseDown scrollWheel mouseDragged mouseMoved mouseUp mouseEntered Viene identificata la posizione dell’evento rispetto allo screen principale e individuato l’eventuale UIView sotto il touch. Poi a seconda del tipo di evento: EVENTO COCOA EQUIVALENTE iOS EVENTI SECONDARI NSLeftMouseDown touchesBegan • [ALT KEY] viene simulato pinch/rotation: il punto del touch diventa il punto intermedio tra due dita • [HOLD] viene simulato l’inizio di una gesture (gestureTouchesBegin) per le gesture registrate al view corrente. NSLeftMouseDragged touchesMoved Se è iniziata una gesture viene inviato l’evento gestureTouchesMoved alle gesture registrate per il view corrente. NSLeftMouseUp touchesEnded Se è in corso una gesture viene inviato l’evento gestureTouchesEnded alle gesture registrate per il view corrente. EVENTI PRINCIPALI
  • 11. Architettura del simulatore Hit test per UIView View A View B View C View D View E hitTest funziona in maniera ricorsiva scendendo nella gerarchia dei view “toccati” fino a quando un subview risponde all’evento (o si torna al root view). !! Consideriamo il touch nel “View E”. La catena sarà: • Il touch è dentro A quindi controlliamo B e C • Il touch non è dentro B, saltiamo • Il touch è dentro C, verifichiamo D ed E • Il touch non è dentro D ma è dentro E, ritorniamo E
  • 12. Architettura del simulatore Hit test per UIView View A View B View C View D View E - (UIView *) hitTest:(CGPoint) point withEvent:(UIEvent *) event { if (hidden || !userInteractionEnabled || alpha < 0.01f) return nil; // se il view è disabilitato o nascosto lo ignoriamo ! BOOL isInside = CGRectContainsPoint(self.frame, point); for (UIView *subview in [subviewsArray reverseObjectEnumerator]) { // convertiamo le coordinate al parent e richiamiamo hitTest CGPoint localPoint = [self convertPoint:point fromView:self.superview]; if (UIView* hitView = [subview hitTest:localPoint withEvent:event]) return hitView; // se cattura l’evento proseguiamo } // avremo il root view di partenza oppure il subview più interno valido return (isInside ? self : nil); } !
  • 13. Architettura del simulatore Ambiente Multi-UIKit E’ possibile supportare più ambienti di UIKit simultaneamente semplicemente impostando una macro. Un nuovo runtime si occupa di creare: • UIApplication • UIScreen • UIDevice 1.Tutti gli oggetti* creati all’interno dell’app devono essere allocati con un riferimento al loro UIKitRuntime. A cascata i figli ereditano il runtime del padre (ex. UIWindow lo eredita da UIScreen) [[oggetto allocWithRuntime:runtime] init… 2. Devono essere prodotte delle macro in grado di redirezionare le chiamate dei singleton. #ifdef UIKitMultipleInstances #define UIApplication ((UIApplication*)self.runtime.application; #else #define UIApplication [UIApplication sharedApplication] (*) tutti quegli oggetti che usano riferimenti al singleton o all’ambiente grafico. UIKitRuntimePool UIKitRuntime UIApplication UIK UIScreen UIK UIWindow UIK UIViewController UIK UIView UIK UIButton UIK [UIApplication sharedApplication] ?
  • 14. Adapter AppKit •Consente di utilizzare oggetti NS* all’interno del simulatore UIKit •Necessario per evitare di implementare i controlli più complessi • UIWebView contiene un adapter per il WebView di OS X • UITextField e UITextView sono implementati come adapter di NSTextField/NSTextView Struttura dell’adapter UIKitLayer (Flipped NSView) UIAppKitAdapter (UIView) CALayer Adapter NSClipView NS* Native Object aggiunto subview di UIKitLayer (dietro tutti gli oggetti) E’ uno UIView contenente l’oggetto Cocoa incapsulato dentro un NSClipView L’adapter acquisisce il layer di NSClipView aggiungendolo alla propria gerarchia La NSClipView viene posizionata allo stesso frame dell’adapter ma all’interno di UIKitLayer principale (una NSView)
  • 15. Adapter AppKit Funzionamento dell’adapter Evento sopra l’adapter (es. mouseDown) UIKitLayer NSClipView - (NSView *) hitTest:(NSPoint) aPoint { NSView *hitNSView = [super hitTest:aPoint]; if (!hitNSView) return nil; ! CGPoint pointInScreen = ... // convertiamo il point nelle coordinate dello UIScreen // verifichiamo se il touch è avvenuto su un adapter, se si procediamo col focus UIView *touchedUIView = [UIMainScreen touchedViewAt: pointInScreen] if ([touchedView isKindOfClass:[UIAppKitAdapter class]]) return hitNSView; else return nil; } UIKitLayer UIAppKitAdapter - (void) viewWillDraw { [adapter updateLayer]; }
  • 16. Qualche controllo UI rilevante •UIScrollView •UITableView •UINavigationController
  • 17. UIScrollView 1/3 • E’ molto simile allo UIView... ...In verità la gran parte dei metodi utilizza proprietà già esposte dallo UIView! • Lo scrolling si basa sull’architettura a layer (“rasterizzazione”+”composizione”) con cui iOS/OSX visualizzano elementi sullo schermo ... proprio come accade con i layer che si usano nei programmi di grafica!
  • 18. UIScrollView 2/3 bounds per il raster (0,0,100,80) 10,20 110,20 u n d i s e g n o t roppo grande 80 100 Rasterization • Si ha un’area di disegno con bounds origin tipicamente 0,0. • Di ogni view viene prodotta la sua rappresentazione (il view non conosce il contesto di destinazione) • Quello che eccede l’area di disegno viene escluso dal raster finale (...non sempre) • Il risultato è la rappresentazione del view Composition • I view vengono montati per livelli da quello più in basso nella gerarchia a quello difronte (frontmost) • La proprietà frame.origin di ogni view indica la sua posizione nel superview di destinazione. 0 LOC X= COMPISITED Superview.frame.origin.y; frame.origin un disegno troppo gr 20,15 100 80 bounds 0,0 110 View.frame.origin.x - Superview.frame.origin.x; Y= View.frame.origin.y - 0 140 20 15
  • 19. UIScrollView 3/3 Ma che c’entra tutto questo con UIScrollView? • Lo scrolling non fa altro che alterare il frame di ogni subview all’interno della scrollview a seconda del movimento delle dita sul touchscreen. ... è un’operazione piuttosto dispendiosa; le scrollview potrebbero avere molte subview • Fino ad ora abbiamo avuto bounds origin del superview 0,0. Potremmo però modificarlo per ottenere uno spostamento (a livello di rasterizzazione) di tutti i view che la compongono, a costo zero! X= View.frame.origin.x - Superview.frame.origin.x; Y= View.frame.origin.y - Superview.frame.origin.y; COMPISITED LOC scrollview bounds {-30,-30,140,110} subview frame {20,15} un disegno troppo gr 20-(-30)=50 15-(-30)=45 • contentOffset lavora proprio in questo modo: ! - (void)setContentOffset:(CGPoint)offset { CGRect bounds = [self bounds]; bounds.origin = offset; [self setBounds:bounds]; } bounds -30,-30 frame.origin 20,15 una porzione del disegno si sposta offscreen (non viene rasterizzata)
  • 20. UITableView 1/3 • E’ tra le classi più usate di iOS (si basa su UIScrollView) • Viene utilizzata spesso per mostrare grandi quantità di dati • ... è quindi necessario che sia performante e con basso impatto sulla memoria Implementazione Lazy VENGONO CREATE SOLO LE CELLE (UIView) VISIBILI NASCOSTI V I S I B I L E 2 AL MOMENTO DELLO SCROLL, LE CELLE CHE 3 1 FINISCONO OFFSCREEN SONO INSERITE IN UNA CACHE PRONTE AD ESSERE RIUTILIZZATE DURANTE LO SCROLL NUOVE RIGHE DOVRANNO ESSERE VISUALIZZATE... SI CERCA NELLA CACHE SE CI SONO CELLE DISPONIBILI CON L’IDENTIFICATORE RICHIESTO DISPONIBILI NON DISPONIBILI SI TOLGONO DALLA CACHE E VENGONO RESE DISPONIBILI SONO ALLOCATE NUOVE ISTANZE
  • 21. UITableView 2/3 UITableView UITableSection #1 Sec. Header Row #1 Row #2 Row #3 Row #n Sec. Footer Header View ..... UITableSection #1 Sec. Header Row #1 Row #2 Row #3 ..... Row #n Sec. Footer Footer View UITableSection #1 Sec. Header Row #1 Row #2 Row #3 ..... Row #n Sec. Footer PER OGNI SECTION DELLA TABLE VIENE CREATA UNA CLASSE CHE NE INCAPSULA LA GEOMETRIA 1 • frame SEC. HEADER • frame PER OGNI ROW • frame SEC. FOOTER • frame globale del blocco (i frame sono rispetto alla table) 2 OTTENUTA LA GEOMETRIA DEGLI ELEMENTI SI RICHIAMA IL METODO CHE SI OCCUPA DI VISUALIZZARE GLI ELEMENTI E GESTIRE LA CACHE - layoutTableStructure reloadData: calcolo della geometria
  • 22. UITableView 3/3 layoutTableStructure: algoritmo per lo scroll • viene chiamata ad ogni cambio di contentOffset (lo scroll) • deve perciò essere molto performante Row #N-1 Section #1 Row #1 INTERSECA L’AREA VISIBILE DELLA TABLE? UITableSection #N Row #N Row #N+1 NULLA. LA CELLA RIMANE VISIBILE. VIENE RIMOSSA E MESSA IN CACHE SI NO 1 2 SI OTTENGONO GLI INDICI DELLE CELLE VISIBILI TRAMITE LA GEOMETRIA. PER QUELLE NUOVE SI CHIEDE AL DATASOURCE. Row #3 Row #2 Section #2 Row #1 Row #2 tableView:cellForRowAtIndexPath: IL DATA SOURCE PUO’ CHIEDERE UNA VERSIONE IN CACHE CON dequeueReusableCellWithIdentifier: VIENE RESTITUITA UNA VERSIONE IN CACHE O UNA NUOVA ISTANZA SE NON DISPONIBILE. 2a 2b UITableView 3 Row #1 • OGNI NUOVA CELLA VIENE INSERITA TRA QUELLE VISIBILI • LA GEOMETRIA CALCOLATA PRIMA CI DA LA SUA POSIZIONE FINALE
  • 23. UINavigationController UINavigationBar UIScrollView UINavigationController Struttura di base View Controller #4 ! (not loaded) n+2 BLOCCO 1 BLOCCO 2 BLOCCO 3 View Controller #2 ! (current) n View Controller #3 ! (push) n+1 View Controller #1 ! (pop) n-1 View Controller #4 ! (cached) n-2 stack navigazione fuori dalla cache x1 x2 • Una UIScrollView contiene il view corrente • Uno stack (NSArray) mantiene la “storia” del navigation • Ad ogni push/pop viene mostrato il view ed eventualmente caricato il view controller precedente e successivo (on demand) • I controller > n+1 possono essere rimossi dalla cache (e deallocati) • I controller < n+1 sono mantenuti per lo stack
  • 24. Grazie dell’attenzione ;-) Daniele Margutti @danielemargutti daniele@creolabs.com