arrow Blog
rss

Noutăți în iOS SDK 5.0 – Totul despre UIPageViewController

November 8th, 2011

Dintre noutățile aduse de iOS SDK5.0, una care mi-a atras în mod deosebit atenția este prezența noii clase UIPageViewController, care permite crearea unei cărți digitale simulând experiența oferită de o carte reală.

UIPageViewController este de fapt un container al cărui rol principal este cel de a gestiona o colecție de view-controllere (care reprezintă paginile cărții digitale), facilitând navigarea între ele.

Opțiuni de afișare

Tranziții

O tranziție reprezintă efectul folosit pentru animarea navigării de la o pagină (adică un view-controller) la alta. Momentan nu este disponibilă decât o singură opțiune – cea care produce un efect asemănător cu cel din iBooks.

Orientarea navigării

Orientarea pe care se face trecerea între “pagini”. Aici există două opțiuni:

- pe orizontală (UIPageViewControllerNavigationOrientation.Horizontal) – trecerea se face de la stânga la dreapta, sau invers;

- pe verticală (UIPageViewControllerNavigationOrientation.Vertical) – trecerea se face de sus în jos, sau invers.

Pivotul folosit pentru animație (Spine)

Pivotul reprezintă axa în jurul căreia se animează trecerea de la o pagină la alta. Există patru opțiuni:

- UIPageViewControllerSpineLocation.None – Fără pivot – Această opțiune nu este însă validă cu tranziția standard;

- UIPageViewControllerSpineLocation.Min – Pivotul se află în partea stângă a paginii (atunci când trecerea se face pe orizontală), sau în partea de sus a paginii (atunci când trecerea se face pe verticală);

- UIPageViewControllerSpineLocation.Max – Pivotul se află în partea dreaptă a paginii (atunci când trecerea se face pe orizontal), sau în partea de jos a paginii (atunci când trecerea se face pe verticală);

- UIPageViewControllerSpineLocation.Mid – Pivotul se află la mijloc, permițând astfel afișarea a două pagini pe ecran. Acest mod se folosește de obicei atunci când device-ul este orientat landscape.

DoubleSided

Această proprietate a UIPageViewController stabiliește câte pagini sunt vizibile la un moment dat: una dacă are valoarea false, două în caz contrar. Atunci când pivotul se află la mijlocul ecranului, DoubleSided trebuie neapărat să fie true.

Dacă are valoarea true și pivotul nu se află la mijlocul ecranului, framework-ul va plasa view-controller-ele noastre și pe spatele paginilor astfel încât vizibile efectiv nu vor fi decât jumătate din ele. Ca atare, DoubleSided nu este folosită în general decât atunci când pivotul este plasat la mijloc.

Un exemplu de implementare

Pentru a exemplifica API-ul concentrat în jurul UIPageViewController am trasat următoarele cerințe:

- vor fi suportate toate orientările posibile ale interfeței dispozitivului;

- pentru orientările de tip landscape vor fi afișate câte două pagini simultan, iar pentru orientările de tip portrait va fi afișată o singură pagină.

Crearea view-controller-ului cu rol de pagină

Pentru a prezenta paginile, va fi nevoie de un view-controller care să gestioneze conținutul unei pagini. Acesta va fi responsabil pentru actualizarea conținutului în funcție de numărul paginii pe care o reprezintă. În plus, va ști să trateze cazul special al unei pagini placeholder, marcată cu numărul 0.

În exemplul nostru acest view-controller nu face decât să afișeze într-un UILabel numărul paginii curente, neavând practic nimic deosebit în construcția sa.

Constructorul clasei primește drept parametru numărul paginii pentru care va fi afișat conținutul. Acest număr poate fi consultat ulterior prin proprietatea read-only CurrentPage.

public PageContentViewController (int page) : base()
{
     mCurrentPage = page;
}
public int CurrentPage
{
	get {
		return mCurrentPage;
	}
}

În plus, trebui să instruim view-controller-ul să suporte toate orientările posibile:

public override bool ShouldAutorotateToInterfaceOrientation
    (UIInterfaceOrientation toInterfaceOrientation)
{
	return true;
}

Crearea DataSource-ului

DataSource-ul este probabil cea mai importantă componentă care trebuie scrisă. Astfel, aceasta este creată extinzând UIPageViewControllerDataSource, care expune două metode ce pot fi suprascrise și implementate:

- GetNextViewController (UIPageViewController pageViewController, UIViewController refViewController) - trebuie să returneze view-controller-ul care-l succede pe cel curent, dat prin al doilea parametru. Primul parametru reprezintă instanța de UIPageViewController care apelează metoda atunci când utilizatorul face “swipe” pentru a naviga la pagina următoare. Dacă nu mai sunt pagini de afișat (adica dacă ne aflăm deja la ultima pagină) atunci vom returna null.

public override UIViewController GetNextViewController
	(UIPageViewController pageViewCtrl, UIViewController refViewCtrl)
{
	int page = ((PageContentViewController)refViewCtrl).CurrentPage;

	if (page == mTotalPages && pageViewCtrl.SpineLocation ==
		UIPageViewControllerSpineLocation.Mid) {
		return new PageContentViewController(0);
	}
	if (page > 0 && page + 1 <= mTotalPages) {
		return new PageContentViewController(page + 1);
	} else {
		return null;
	}
}

Implementarea prezentată aici este destul de simplă și nu voi insista decât pe liniile 6-9. Atunci când pivotul este situat la mijlocul ecranului (fiind deci afișate câte două pagini simultan), la fiecare gest de navigare făcut de utilizator UIPageViewController-ul apelează de două ori succesiv această metodă pentru a obține paginile ce vor fi afișate, iar dacă vreunul din apeluri returnează null, atunci navigarea nu va mai avea loc. Acest caz este întâlnit atunci când avem un număr impar de pagini. Ca atare, am ales ca ultima pagină să fie o pagină placeholder, marcată cu numărul 0.

- GetPreviousViewController (UIPageViewController pageViewController, UIViewController refViewController) – trebuie să returneze view-controller-ul care-l precedă pe cel curent, sau null dacă pagina curentă este prima pagină. Implementarea este asemănătoare cu cea a GetNextViewController:

public override UIViewController GetPreviousViewController
	(UIPageViewController pageViewCtrl, UIViewController refViewCtrl)
{
	int page = ((PageContentViewController)refViewCtrl).CurrentPage;

	if (page > 0 && page - 1 >= 1) {
		return new PageContentViewController(page - 1);
	} else {
		return null;
	}
}

În plus, pentru conveniență, am adăugat o nouă metodă, GetViewControllers, care primește doi parametri:

- poziționarea pivotului;

- numărul paginii de referință.

Rezultatul execuției este un vector care conține:

- două view-controllere (primul corespunde paginii de referință, iar cel de-al doilea paginii următoare, dacă aceasta există, sau unei pagini placeholder în caz contrar) , dacă pivotul este poziționat la mijlog

- un singur view-controller, corespunzător paginii de referință, în celelalte situații.

Metoda este apelată atât la selectarea view-controller-elor afișate inițial, cât și la schimbarea orientării dispozitivului, pentru a determina care sunt paginile ce trebuie afișate.

public PageContentViewController[] GetViewControllers
    (UIPageViewControllerSpineLocation spine, int fromPage)
{
	List<PageContentViewController> viewControllers =
		new List<PageContentViewController>(2);

	if (mTotalPages == 0) {
		return new PageContentViewController[] {};
	}
	if (fromPage > mTotalPages) {
		fromPage = mTotalPages;
	}

	viewControllers.Add(new PageContentViewController(fromPage));
	if (spine == UIPageViewControllerSpineLocation.Mid) {
		if (fromPage + 1 > mTotalPages) {
			fromPage = 0;
		} else {
			fromPage ++;
		}
		viewControllers.Add(new PageContentViewController(fromPage));
	}

	return viewControllers.ToArray();
}

Crearea Delegate-ului

Implementarea Delegate-ului este în principiu opțională, dar absolut necesară în cazul nostru, deoarece ne dorim să variem poziția pivotului (și implicit numărul de pagini afișate) în funcție de orientarea interfeței dispozitivului.

Pentru a obține acest lucru trebuie să suprascriem metoda GetSpineLocation care primește doi parametri:

- UIPageViewController-ul care o apelează;

- orientarea interfeței dispozitivului.

public override UIPageViewControllerSpineLocation GetSpineLocation
	(UIPageViewController pageViewCtrl, UIInterfaceOrientation o)
{
	BookDataSource ds = (BookDataSource)pageViewCtrl.DataSource;
	UIPageViewControllerSpineLocation spine;
	PageContentViewController page =
		(PageContentViewController)pageViewCtrl.ViewControllers[0];
	int startPage = page.CurrentPage;

	if (o == UIInterfaceOrientation.LandscapeLeft ||
		o == UIInterfaceOrientation.LandscapeRight) {
		spine = UIPageViewControllerSpineLocation.Mid;
		pageViewCtrl.DoubleSided = true;
		if (page.CurrentPage % 2 == 0) {
			startPage = page.CurrentPage - 1;
		}
	} else {
		spine = UIPageViewControllerSpineLocation.Min;
		pageViewCtrl.DoubleSided = false;
	}

	pageViewCtrl.SetViewControllers(
		ds.GetViewControllers(spine, startPage),
		UIPageViewControllerNavigationDirection.Forward,
		true, delegate(bool finished) {
			return;
	});
	return spine;
}

În exemplul de față sunt parcurși următorii pași:

- este determinată noua poziție a pivotului;

- instruim UIPageViewController-ul să afișeze una sau două pagini (via proprietatea DoubleSided, care trebuie să fie obligatoriu setată la valoarea true atunci când pivotul este situat la mijloc);

- setăm noile view-controllere care vor fi afișate (spre exemplu, dacă interfața este orientată portrait, afișând o singură pagină – pagina 7, și utilizatorul schimbă orientarea în landscape, vor fi afișate view-controller-ele corespunzătoare paginilor 7 și 8).

Crearea container-ului care va coordona activitatea

Pentru a folosi un UIPageViewController vom avea nevoie de un container pentru acesta. În exemplul nostru, rolurile acestui container sunt după cum urmează:

- creează o instanță de UIPageViewController, pe care o configurează cu DataSource-ul și Delegate-ul corespunzătoare;

- setează view-controller-ele afișate inițial, folosind metoda UIPageViewController.SetViewControllers;

- implementează metoda ShouldAutorotateToInterfaceOrientation, pentru a suporta toate orientările posibile ale unui dispozitiv.

public override void ViewDidLoad ()
{
	UIViewController[] viewControllers = null;
	base.ViewDidLoad ();

	mDs = new BookDataSource();
	mDlg = new BookDelegate();

	mBook = new UIPageViewController(
		UIPageViewControllerTransitionStyle.PageCurl,
		UIPageViewControllerNavigationOrientation.Horizontal,
		UIPageViewControllerSpineLocation.Min
	);
	viewControllers = mDs.GetViewControllers(mBook.SpineLocation, 1);

	mBook.DataSource = mDs;
	mBook.Delegate = mDlg;
	mBook.View.Frame = View.Bounds;

	mBook.SetViewControllers(
		viewControllers,
		UIPageViewControllerNavigationDirection.Forward,
		false, delegate(bool finished) {
			return;
	});

	View.AddSubview(mBook.View);
	AddChildViewController(mBook);
}

Metoda UIPageViewController.SetViewControllers primește următorii parametri:

- Un vector care conține view-controller-ele ce vor fi afișate. Ca regulă, acest vector conține două view-controllere atunci când pivotul este UIPageViewControllerSpineLocation.Mid și unul singur în celelalte cazuri;

- Direcția de navigare către noile view-controllere: UIPageViewControllerNavigationDirection.Forward (navigare înainte) sau UIPageViewControllerNavigationDirection.Reverse (navigare înapoi). De reținut faptul că aceast parametru nu afectează decât modul în care este animată tranziția curentă, neavând efect asupra tranzițiilor următoare;

- Dacă tranziția este animată sau nu;

- Un callback ce va fi apelat atunci când tranziția este finalizată. Acest callback trebuie să primească un parametru de tip boolean care este true dacă animația a fost finalizată, respectiv false dacă animația nu a fost executată.

În plus, trebuie menționat că fără a adăuga explicită UIPageViewController-ul drept copil al container-ului nostru, metoda GetSpineLocation a Delegate-ului nu va fi apelată, de unde utilitatea liniei 28.

Folosirea acestor clase

Odată create aceste clase, utilizarea lor este simplă și se reduce la a adăuga container-ul nostru în ierarhia de view-uri a aplicației:

[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
	private UIWindow window;

	private BookViewController mBook = null;

	public override bool FinishedLaunching
            (UIApplication app, NSDictionary opts)
	{
		mBook = new BookViewController();

		window = new UIWindow (UIScreen.MainScreen.Bounds);
		window.RootViewController = mBook;
		window.MakeKeyAndVisible ();

		return true;
	}
}

Click aici pentru a descărca proiectul care exemplifică toate aceste concepte.

Taguri: , ,
292 afisari

Comenteaza

*