niedziela, 8 lutego 2015

Kulisy Xamarina - odc. 4: jeszcze trochę o iOS

Rozwinięcie niektórych zagadnień związanych z iOS i środowiskiem Xamarin. Do technicznie bardziej ciekawych z pewnością należy bindowanie bibliotek Objective-C z poziomu C# oraz framework  MonoTouch.Dialog do szybkiej budowy UI w oparciu o metadane lub opis strukturalny.  Poruszyłem też temat obsługi iBeaconów. Nie jest rzecz typowo związana z Xamarin, ale z pewnością godna uwagi. Przy okazji mamy przykładowe użycie centrum notyfikacyjnego i notyfikacji.

 

Ogólne uzupełnienia

.NET BCL + iOS SDK + Xamarin.Mobile

możliwa współpraca pomiędzy Objective-C i C#

kompilacja do natywnego maszynowego kodu - AOT (Ahead of Time Compilation) –> natywne assembly ARM

możliwość stosowania bibliotek .NET i Objective-C

http://developer.xamarin.com/guides/ios/advanced_topics/binding_objective-c/

 

Ustawienia

Settings.bundle/Root.plist

odczyt:

kontroler

public override void ViewWillAppear(bool animated)

{

          Label.Text = NSUserDefaults.StandardUserDefaults.StringForKey(“xxx”);

          base.ViewWillAppear(animated);

}

zapis:

NSUserDefaults.StandardUserDefaults.SetString(“wartosc”, “xxx”);

rejestracja domyślnych wartości:

AppDelegate FinishedLaunching

var settingsBundle = NSBundle.MainBundle.PathForResource(“Settings”, @”bundle”);

var keyString = new NSString(@”Key”);

var defaultString = new NSString(@”DefaultValue”);

var settings = NSDictionary.FromFile(Path.Combine(settingsBundle, @”Root.plist”);

var preferences = (NSArray) settings.ValueForKey(new NSString(@”PreferenceSpecifiers”);

var defaultsToRegister = new NSMutableDictionary();

for (uint i=0; i < preferences.Count; i++)

{

       var prefSpecification = new NSDictionary(preferences.ValueAt(i));

       var key = (NSString) prefSpecification.ValueForKey(keyString);

       if (key != null)

       {

               NSObject def = prefSpecification.ValueForKey(defaultString);

               if (def != null)

                     defaultsToRegister.SetValueForKey(def, key);

       }

}

NSUserDefaults.StandardUserDefaults.RegisterDefaults(defaultsToRegister);

 

Actions Sheets & Alerts

var alert = new UIAlertView(“Tytuł”, “Wiadomość”, null, “OK”);

alert.Show();

 

var alert = new UIAlertView(“Tytul”, “Pytanie”, null,  “Anuluj”, “OK”);

//alert.Canceled

alert.Clicked += HandleAlertClicked;

alert.Show();

 

void HandleAlertClicked (object sender, UIButtonEventArgs e)

{

         var alert = (UIAlertView) sender;

         if (e.ButtonIndex == alert.CancelButtonIndex)

                …

         else

                …

}

 

var actionSheet = new UIActionSheet(“Tytuł/pytanie”, null, “Anuluj”, “Usuń”, “Inna akcja”);

actionSheet.Clicked += HandleActionSheetClicked;

actionSheet.ShowInView(View);

 

void HandleActionSheetClicked(object sender, UIButtonEventArgs e)

{

         var actionSheet = (UIActionSheet)  sender;

         if (e.ButtonIndex == actionSheet.DestructiveButtonIndex)  //destructive - 0, cancel - ostatni

                …

         if (e.ButtonIndex == 1)  // actionSheet.FirstOtherButtonIndex

                …   

         if (e.ButtonIndex == actionSheet.CancelButtonIndex)  

                … 

}

 

Drobne uzupełnienia

Grupowanie w UITableView:

UITableViewDataSource / UITableViewSource

public override int NumberOfSections(UITableView tableView)

{

}

public override string TitleForHeader(UITableView tableView, int section)

{

}

public override string TitleForFooter(UITableView tableView, int section)

{

}

Obrazek w komórce UITableView:

cell.ImageView.Image = new UIImage(“xxx.jpg”);

Przycisk szczegółów w komórce UITableView:

cell.Accessory = UITableViewCellAccessory.DetailDisclosureButton;

Chowanie przyciskiem Return klawiatury w polu tekstowym:

textField.ShouldReturn = (s) =>

{

         textField.ResignFirstResponder();

         return true;

}

Ukrywanie paska nawigacyjnego na danym ekranie:

public override void ViewDidAppear(bool animated)

{

          …

          NavigationController.SetNavigationBarHidden(true, animated);

}

public override void ViewWillDisappear(bool animated)

{

          NavigationController.SetNavigationBarHidden(false, animated);

}

Zakładki:

public class TabBarController: UITabBarController

{

          UIViewController tab1;

          UIViewController tab2;

          public TabBarController()

          {

          }

          public override void ViewDidLoad()

          {

                    base.ViewDidLoad();

 

                    tab1 = new XViewController();

                    tab1.Title = “Xxx”;

                    tab1.TabBarItem = new UITabBarItem(UITabBarSystemItem.History, 0);  //obrazek

                    …

                    var tabs = new UIViewController [] { tab1, tab2 };

                    ViewControllers = tabs;

 

                    SelectedViewController = tab1;                   

          }

}

Wyświetlenie modalne:

var controller = new XViewController();

controller.ModalTransitionStyle = UIModalTransitionStyle.PartialCurl;

PresentViewController(controller, true, () => { //on completed });

 

MonoTouch.Dialog

Framework do łatwiejszego tworzenia UI, w tym dynamicznie definiowanego.

  • wsparcie dla wielu różnych kontrolek
  • pull to refresh (odświeżanie widoku po umieszczeniu danych w TableView)
  • search
  • JSON (definiowanie UI)
  • ładowanie obrazków w tle
  • duża rozszerzalność

http://developer.xamarin.com/guides/ios/user_interface/monotouch.dialog/

Jak to działa ?

  • Reflection API - jak ORM, mapowanie metadanych/atrybutów na obiekty
  • Elements API - odczytywanie drzewa obiektów i jego rysowanie

Główne elementy:

  • DialogViewController - dziedziczy po TableView
  • RootElement - reprezentuje stronę
  • Section - podobne do sekcji w TableView
  • Element – kontrolka

Dodajemy referencję do projektu.

public class Person

{

         public Person()

         {

         }

 

         [Entry(“Podaj imię i nazwisko”)]

         public string Name;

 

         [Entry(KeyboardType = UIKeyboardType.NumberPad)]

         public string Age;

 

         [Date]

         public DateTime Birthday = DateTime.Now;

 

         [OnTap(“ButtonClicked”)]

         public string Button; 

}

//budowa kontrollera

var person = new Person();

var binding = new BindingContext(this /* jeśli mamy callbacki, inaczej null*/, person, “Moje dane”);

var dialogViewController = new DialogViewController(binding.Root);

var navigationController = new UINavigationController(dialogViewController);

public void ButtonClicked()

{

}

Elements API:

var root = new RootElement(“Xxx”)  {   //skrót do Add(…)

        new Section {

                new RootElement(“Day 1”)  {   //podstrona

                        new Section  {   //skrót do Add(…)

                                 new StringElement(“12 grams”),

                                 new BooleanElement(“yyy”, true /*wartość*/)

                        }

                }

        }

}

var dialogViewController = new DialogViewController(root);

var navigationController = new UINavigationController(dialogViewController);

 

iOS 5 - notatka z historii

  • Cloud Storage
  • AirPlay Mirroring (Apple TV)
  • Centrum notyfikacyjne
  • Newsstand
  • Integracja z Twitterem
  • iMessage
  • Storyboards
  • Rozszerzenia Core Image
  • Emulacja lokalizacji

 

iCloud

Prawdziwe urządzenie

Opcja aplikacji na portalu

Ustawienia projektu VS

iOS Application –> Entitlements

  • iCloud Key-Value Store
  • iCloud Services
  • iCloud Containers

iOS Bundle Signing –> Custom entitlements (= Entitlements.plist)

odczyt:

var store = NSUbiquitousKeyValueStore.DefaultStore;

store.Synchronize();

Label.Text = store.GetString(“Xxx”);

zapis:

var store = NSUbiquitousKeyValueStore.DefaultStore;

store.SetString(“Xxx”, “wartosc”);

store.Synchronize();

 

iBeacons

Beacon

  • każde urządzenie BLE (Bluetooth Low Energy) (Bluetooth 4.0+)
  • urządzenie BLE, które transmituje sygnał pozwala odbierającemu urządzeniu wykryć swoją bliskość
  • nie wymagają parowania

iBeacon

  • termin i specyfikacja Apple
    • w jaki sposób są rozpoznawane przez urządzenia Apple
    • broadcast interval
    • advertisement/message format

iBeacon Advertisements

do 31 bajtów

  • Prefix: 9
  • Proximity UUID: 16
  • Major: 2
  • Minor: 2
  • Tx Power: 1

Wspierane platformy

  • iOS7+
  • Android 4.3 (Jelly Bean+)
  • Windows Phone 8 (Update 3+)

Warunki i ograniczenia

  • Bluetooth musi być włączony
  • Location Services dla aplikacji muszą być udostępnione
  • wymagany jest dostęp do Internetu (content & location services)
  • maksymalnie 20 regionów
  • do testowania potrzebne jest fizyczne urządzenie
  • iOS nie może skanować wszystkich używanych UUID-ów
  • aplikacje mają aktywny “zasięg” tylko w foreground (wyjątek: 10 s otrzymywania danych o zakresie przy zdarzeniach didEnter i didExitRegion)

Sposób działania

  • iOS Core Location Services, Bluetooth 4.0
  • definicja regionów przy użyciu UUID, [Major], [Minor]
  • aplikacja nasłuchująca sygnałów beacona
  • unikalny experience bazujący na zbliżeniu przy wchodzeniu/opuszczaniu regionów/zakresów beaconów

Core Location Manager  )))

  • StartMonitoring
  • StartRangingBeacons

Core Location Manager Delegate  (((

  • RegionEntered
  • RegionLeft
  • DidRangeBeacons 
  • DidDetermineState

Cztery stany

  • Unknown:  > 30 m
  • Far:  2m - 30m (mała dokładność lub słaby sygnał)
  • Near:  0.5 - 2m (całkiem duża dokładność)
  • Immediate:  0 - 0.5m (wysoka dokładność)

Ufanie bliskości

  • Transmit Power (TXPower)
  • Received Signal Strength (RSSI)
  • Accuracy
  • Broadcast Interval

Kod

//definicja regionu

CLBeaconRegion region;

region = new CLBeaconRegion(new NSUuid(“B9407F30-F5F8-466E-AFF9-25556B57FE6D”), “XXX  Region”);

region.NotifyEntryStateOnDisplay = true;

region.NotifyOnEntry = true;

region.NotifyOnExit = true;

 

//nasłuchiwanie

[Register (“AppDelegate”)]

public partial class AppDelegate: UIApplicationDelegate

{

         …

         private CLLocationManager _locationManager;

         public CLLocationManager ShareLocationManager

         {

                  get

                  {

                           if (_locationManager == null)

                           {

                                     _locationManager = new CLLocationManager();

                                     _locationManager.Delegate = new CoreLocation();

                            }

                           return _locationManager;

                  }

         }

         …

         public override void OnActivated(UIApplication application)

         {

                   ShareLocationManager.StartMonitoring(GetRegion());

         }

}

public class CoreLocation: CLLocationManagerDelegate

{

          public CoreLocation() {}

          public override void RegionEntered (CLLocationManager manager, CLRegion region)

          {

                   manager.StartRangingBeacons(region as CLBeaconRegion);    //region.Identifier

                   NSNotificationCenter.DefaultCenter.PostNotificationName(“RegionUpdate”, this,

                   new NSDictionary (“RegionData”,  content /* instancja obiektu z danymi, dziedziczącego po NSObject*/);

          }

          public override void RegionLeft (CLLocationManager manager, CLRegion region)

          {

                   manager.StopRangingBeacons(region as CLBeaconRegion);    

          }

          //foreground: co sekundę dla monitorowanego regionu, background/closed: 10 s po wejściu/opuszczeniu regionu

          //budzenie ekranu (NotifyEntryStateOnDisplay = true): 10 s, warunek: wewnątrz monitorowanego regionu

          public override void DidRangeBeacons (CLLocationManager manager, CLBeacon[] beacons, CLBeaconRegion region)

          {

                   if (beacons.Length > 0)

                   {

                            var beacon = (CLBeacon) beacons.GetValue(0);

                            //beacon.ProximityUuid,  .Major, .Minor, .Proximity,  .Rssi, .Accuracy

                   }

          }

          public override void DidDetermineState (CLLocationManager manager, CLRegionState state, CLRegion region)

          {

                    if (state = CLRegionState.Inside)

                    {

                            manager.StartRangingBeacons(region as CLBeaconRegion);

                    }

          }

}

public partial class XViewController: UIViewController

{

          NSObject _regionNotify;

          …

          public override void ViewWillAppear(bool animated)

          {

                   base.ViewWillAppear(animated);

                   _regionNotify = NSNotificationCenter.DefaultCenter.AddObserver((NSString)”RegionUpdate”, OnRegionUpdate);

                  …

          }       

          public void OnRegionUpdate(NSNotification notification)

          {

                   var content = notification.UserInfo.ValueForKey((NSString)”RegionData”) as XContent; /*nasza klasa z danymi */

                   …

          }

          …

          public override void ViewWillDisappear(bool animated)

         {

                  _regionNotify.Dispose();

                  base.ViewWillDisappear(animated);

         }

}

Zdarzenia beacon-ów w tle lub przy wyłączonej aplikacji

[Register (“AppDelegate”)]

public partial class AppDelegate: UIApplicationDelegate

{

         …

         public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)

         {

                 ShareLocationManager.StartMonitoring(GetRegion());

                 return true;

         }

}

public class CoreLocation: CCLocationManagerDelegate

{

         //obiekt używany do wysyłania danych przy obsłudze zdarzeń

         private XContent _content = new XContent();

         public void SetContent(XContent content)

         {

                 if (content != null)

                 {

                         if (UIApplication.SharedApplication.ApplicationState == UIApplicationState.Active) //foreground

                               NSNotificationCenter.DefaultCenter.PostNotificationName(“ContentUpdate”, this,

                               new NSDictionary (“Content”, content));

                         else //background

                              NotificationHelper.SendLocalAlertNotification(content);                        

                 }

         }

        …

        //DidRangeBeacons: wysyłamy jeśli beacon.Proximity != CLProximity.Unknown i aplikacja jest w foreground

}

public class NotificationHelper

{

         …

         public static void SendLocalAlertNotification(XContent message, int delayInSeconds = 1)

         {

                 var notification = new UILocalNotification();                

 

                 notification.FireDate = DateTime.Now.AddSeconds(delayInSeconds);

                 notification.AlertAction = message.NotificationTitle;

                 notification.AlertBody = message.NotificationMessage.ToString();

 

                 notification.ApplicationIconBadgeNumber += 1;

 

                 notification.SoundName = UILocalNotification.DefaultSoundName;

 

                 UIApplication.SharedApplication.ScheduleLocalNotification(notification);                

         }

}

 

Visual Studio na Mac-u

Wirtualna maszyna

  • Parallels Desktop
  • VMware Fusion

Boot Camp - program Apple umożliwiający uruchamianie Windows

Brak komentarzy: