Archivi tag: Objective-C

cocoa_cup

OSX e le risorse dinamiche

Molte applicazioni di piccole dimensioni nascono e muoiono con il solo contributo delle risorse definite nel nib/xib MainMenu contenuto nel bundle dell’applicazione.
Ma la disponibilità di risorse nella programmazione macOS è molto più articolata e al crescere della dimensione e funzionalità di una applicazione questa disponibilità verrà prima o poi a convergere nella nostra applicazione.

Vediamo di dare una guida ragionata all’uso di questa disponibilità di risorse.

Le diverse forme di disponibilità vanno scelte in ragione della tipologia di risorsa che intendiamo dinamizzare ed in ragione di quante istanze di questa abbiamo bisogno.

Una ulteriore scelta potrebbe essere fatta in ragione del budle che contiene le risorse che ci interessa utilizzare: potremmo infatti considerare l’uso di risorse collocate diversamente dal bundle dell’applicazione; ma per ora tralasciamo questa possibilità.

Iniziano con il distinguere in base alla dimensione della astrazione che intendiamo recuperare dinamicamente: una intera finestra o una vista custom.

All’interno di questa distinzione possiamo trovare una ulteriore dicotomia in relazione al numero di controller collegati alle viste: un solo controller o più controller.

Pur potendo il nib/xib contenere istanze di oggetti controller, quello che a noi interessa è l’istanza (o istanze) di controller che richiedano il caricamento dinamico di risorsa, e non quindi controller che subiscano essi stessi il meccanismo di caricamento dinamico stesso.

Dal punto di vista del nib/xib esiste un riferimento speciale ad un oggetto che è inteso sempre disponibile per le viste ivi contenute perché responsabile del caricamento dell’intero nib/xib: questo riferimento è il File’s Owner. Si può considerarlo spesso un oggetto di tipo controller o comunque un proxy di qualche genere a modello o azioni (a cui collegare le viste).

In virtù di questo il framework AppKit provvede a fornirci due implementazioni di controller con cui governare (prima delle altre cose accessorie in grado di fare) il caricamento dinamico di una intera finestra o di una specifica vista; sono: NSWindowController e NSViewController. L’implementazione di base è spesso sufficiente allo scopo del caricamento dinamico, comunque non è infrequente che si possa necessitare di una loro estensione per compiti poi specifici.
Perché le viste all’interno del nib/xib abbiano accesso ad un controller o model validi è sufficiente fare affidamento al File’s Owner, definendolo nel nib/xib come di classe scelta per il controllo del caricamento della risorsa rappresentata nel nib/xib (quindi NSWindowController o NSViewController, oppure loro derivazioni).
A questo punto una istanza di NSWindowController potrà tramite -(id)initWithWindowNibName:(NSString *)windowNibName
consentire il recupero di una intera finestra da un nib/xib (indicato per nome) con tutte le azioni, outlet o binding collegate a File’s Owner (e quindi NSWindowController) o suoi elementi.
La finestra stessa è ottenibile dall’istanza di NSWindowController mediante metodo -window, così come sono ottenibili altri strumenti propri del controller.

Esempio

NSWindowController * aWindowController = [[NSWindowController alloc] initWithWindowNibName:@"myWindow"];

[aWindowController showWindow: self];

Allo stesso modo un NSViewController potrà farsi carico del caricamento di un nib/xib che definisce una vista attraverso il metodo:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

L’idea di questo controller (contrariamente al precedente, orientata alla finestra/documento) è maggiormente sviluppata al supporto per le viste contenute nel nib/xib nella definizione di legami con il modello; a tale scopo fornisce una proprietà generica representedObject per consentire un facile collegamento tra il nib e un oggetto (modello) che ancora non è noto in fase di caricamento del nib/xib.

Esempio

NSViewController *viewCtlr = [[[NSViewController alloc] initWithNibName:@"myView" bundle:nil] autorelease];

[[window contentView] addSubview: [viewCtlr view]];

Questi due controller risolvono il problema di una vista più o meno complessa governata da un proprio controller. Ma come poter gestire istanze multiple di viste custom afferenti al medesimo controller?

Una derivazione da NSView può implementare la seguente:

- (BOOL)loadFromNib:(NSString *)nibName bundle:(NSBundle *)bundle controller:(id)controller
{
/*
 * Codice liberamente tratto da "Resource Programming Guide" di Apple
 *
 */

    NSNib *		aNib = [[NSNib alloc] initWithNibNamed:@"ReusableView" bundle:bundle];

    NSArray *	topLevelObjs;

    if (![aNib instantiateNibWithOwner:controller topLevelObjects:&topLevelObjs]) {

     NSLog(@"Warning! Could not load nib file.\n");

     return NO;

    }

    // Ricerca delle viste

   for(id obj in topLevelObjs) {

     if ( [obj isKindOfClass: [NSView class]] )

        [self addSubview: obj];

}

   // Release the raw nib data.

    [aNib release];

    // Release the top-level objects so that they are just owned by the array.

    [topLevelObjs makeObjectsPerformSelector:@selector(release)];

   // Do not autorelease topLevelObjs.

    return YES;

}

Esempio

ReusableView * reusable1  = [[ReusableView alloc] initWithFrame:NSMakeRect(0, 0, 100, 95)];

[reusable1 loadFromNib:@"ReusableView" bundle:nil controller: controller];

[reusable2 loadFromNib:@"ReusableView" bundle:nil controller: controller];

Un limite di questa implementazione è l’assenza di un meccanismo automatico per definire la catena dei risponditori sommando il contributo di ciascuna vista (e poter passare da una a l’altra, che detengono così catene indipendenti).
Per semplificare rapporto con model implementare una proprietà representedObject similmente a NSViewController.

Va da se che applicare questa forma di caricamento quando si collegano poi alle viste istanze di controller differenti è una inutile ripetizione della implementazione già in essere con NSViewController.
Con questa ultima osservazione chiudiamo la nostra guida al caricamento dinamico delle risorse in macOS.

Alla prossima.

IMG_3049.PNG

Estendere le capacitˆà di stampa di Cocoa

Avete realizzato l’interfaccia per cdlabelgen come descritto nel precedente post?

Beh, io si e ho affrontato anche il problema della stampa: voi?

La stampa in programmi Cocoa deriva dall’essenziale derivazione del motore grafico Quartz dal PDF. Quindi in Cocoa la stampa nasce dalla costruzione di un suo oggetto NSView da cui discendano come contenuto tutte le componenti grafiche necessarie a strutturare il layout finale.

Quello che otteniamo è dunque una relazione 1:1 tra widget Cocoa, testo e immagini e le componenti PDF che porteremo alla stampa.

Ma con cdlabelgen abbiamo a che fare con postscript: questo è ciò che viene generato dallo script. Che PDF e postscript siano parenti non aiuta affatto!

Cercando in Internet non ho trovato soluzioni, nemmeno lontanamente. Che la cosa non fosse mai stata affrontata o fosse stata risolta con librerie proprietarie dai software di grafica con tanto di blasone mi sembrava alquanto strana.

Poi (la disperazione a volte aiuta!) mi sono ricordato che nella cartella esempi di XCode qualcosa riguardo la stampa era presente: ho cominciato a cercare e studiare, tornando sull’indispensabile (e preziosa) documentazione ADC.

Ed ecco a voi la soluzione (Apple, non mia: io ho solo ritrovato e ora pubblicizzato tale soluzione).

Dicevamo che i frameworks che compongono l’universo Cocoa (in particolare AppKit) supportano una limitata visione del processo di stampa, segnatamente una versione NSView-centrica gestita attraverso le seguenti astrazioni:

  • NSPageLayout
  • NSPrintInfo
  • NSPrintOperation
  • NSPrintPanel
  • NSPrinter

Un supporto completo alla stampa deriva (da indicazione esplicita in documentazione) dall’utilizzo diretto del framework Core Printing, una API in C di cui AppKit crea una interfaccia limitata.

La soluzione da noi cercata per stampare dati postscript (file, in particolare) è la funzione PMPrinterPrintWithFile presente in questo framework; il suo prototipo è:

OSStatus PMPrinterPrintWithFile (

 PMPrinter printer,

 PMPrintSettings settings,

 PMPageFormat format,

 CFStringRef mimeType,

 CFURLRef fileURL

);

Come possiamo vedere mediante il parametro fileURL si fornisce un percorso al file da stampare, file di cui mediante parametro mimeType si indica anche il tipo. La specifica di tipo è importante in quanto deve coincidere con uno dei tipi supportati dal framework; i tipi supportati (ricavati dalla funzione per la validazione del tipo passato) sono:

  •     “application/octet-stream”
  •     “application/pc-eps”
  •     “application/pdf”
  •     “application/pictwps”
  •     “application/postscript”
  •     “application/vnd.apple-postscript”
  •     “application/vnd.cups-banner”
  •     “application/vnd.cups-command”
  •     “application/vnd.cups-postscript”
  •     “application/vnd.cups-raster”
  •     “application/vnd.cups-raw”
  •     “application/vnd.hp-hpgl”
  •     “application/x-cshell”
  •     “application/x-csource”
  •     “application/x-perl”
  •     “application/x-shell”
  •     “application/xhtml+xml”
  •     “image/fuji-raw”
  •     “image/gif”
  •     “image/imageio”
  •     “image/jp2”
  •     “image/jpeg”
  •     “image/minolta-raw”
  •     “image/openexr”
  •     “image/pict”
  •     “image/png”
  •     “image/rad”
  •     “image/tiff”
  •     “image/x-alias”
  •     “image/x-bitmap”
  •     “image/x-bmp”
  •     “image/x-ico”
  •     “image/x-icon”
  •     “image/x-photocd”
  •     “image/x-portable-anymap”
  •     “image/x-portable-bitmap”
  •     “image/x-portable-graymap”
  •     “image/x-portable-pixmap”
  •     “image/x-psd”
  •     “image/x-qtif”
  •     “image/x-sgi-rgb”
  •     “image/x-sun-raster”
  •     “image/x-tga”
  •     “image/x-xbitmap”
  •     “image/x-xpixmap”
  •     “text/html”
  •     “text/plain”
  •     “text/rtf”

Non male, vero?

È ovvio che per invocare questa funzione occorre completarne la lista argomenti: stampante, formato e settaggio per il processo di stampa.
Questi ultimi due parametri li ricaviamo fortunatamente direttamente in AppKit, in particolare da una istanza condivisa di NSPrintInfo 

 [NSPrintInfo sharedPrintInfo]

dopo averla popolata utilizzando NSPrintPanel e NSPageLayout (le componenti Cocoa che realizzano le finestre di dialogo utente per le rispettive finalitˆà utilizzando una istanza di NSPrintInfo). Per Il parametro stampante invocheremo PMSessionGetCurrentPrinter() passando la sessione ottenuta sempre da NSPrintInfo dopo averle collegato settaggio e formato mediante PMSessionDefaultPrintSettings() e PMSessionValidatePageFormat() (funzioni di Core Printing).

Mettendo assieme il tutto avrete la vostra stampa di file postscript.
Questo un rapido quadro della soluzione: per i dettagli c’e’ la documentazione: replicarla qui mi sembra troppo!

Alla prossima!