niedziela, 31 stycznia 2016

Być jak Joe Belfiore…

Zastanawiam się co Joe widzi w tym urządzeniu, trzeba pewnie bardziej się w nim “zanurzyć”, jak sam mówi. 

System updatów działa sprawnie. Fajna app-ka Garage Band, można pograć sobie na syntetytorze i klawiszach. Takiej wbudowanej nie ma na innych platformach –;)  3D Touch wydaje się trochę przereklamowany, ale przydatny. Im więcej go się użyje tym może wydawać się lepszy. Bez klawisza Back można się obejść, aczkolwiek ciągle gdzieś mam go w głowie i teraz szukam opcji nawigacyjnej w górnym menu, by powrócić, ale to kwestia przyzwyczajenia z Windows Phone/Mobile i Android-a.  Pomysłowe kółka pokazujące progress przy instalacji app-ek z App Store.

środa, 27 stycznia 2016

14251

Dziś wypadł dzień nowego buildu Windows 10 (desktop).  Generalnie nic nowego, ale mamy przeskok znaczny w numerze kompilacji w wyniku połączenia numeru z kompilacjami Mobile, które miały wyższe numerki. Poza tym nie działają F12 w Edge  -;)

Announcing Windows 10 Insider Preview Build 14251

niedziela, 24 stycznia 2016

Poznać lepiej 2-go wroga - iOS 9 preludium

Ha, dziś coś na temat drugiego “wroga” (z punktu fana platformy Windows –;)), takie mentalne przygotowanie na żywy kontakt z iOS 9 (jeśli chodzi o pierwszego “wroga” to napiszę za jakiś czas “przypowieść o kablu”).

Jak się poznaje pewne techniczne rzeczy z poziomu dev-a, czasami wrażenia z punktu widzenia użytkownika mogą zejść na dalszy plan. Tak może się stać z designem i user experience najnowszego iOS, którym się dziś przyjrzałem. Oczywiście w oficjalnej dokumentacji odnajdziemy miejsca temu poświecone:

Najciekawszy z tego wszystkiego jest IMHO 3D Touch, którego uświadczymy na iphonach 6s i 6s plus (na ipadach póki co nie ma takiej funkcjononalności). Na ekranie startowym kojarzy mi się z tym, co dają secondary tiles, tylko że takie dodatkowe wejścia do app-ki pojawiają się przy odpowiedniej sile nacisku. 3D Touch pozwala też na pokazywanie dodatkowych informacji / akcji dla elementów wewnątrz aplikacji przed ich otworzeniem.

Interesująco przedstawia się IMHO także HomeKit do zarządzania inteligentnym domem, a na ipad-ach oczywiście należy pamiętać o możliwości pracy z więcej niż jedną aplikacją na ekranie. 

Guidelin-ów na temat designu iOS jest oczywiście wiele, np. dwa pierwsze rozdziały jednej książki:

Można też sobie spojrzeć na system i jego wbudowane aplikacje, kartkując przewodnik dla użytkownika iOS:

Jednej rzeczy nie zawierają te przewodniki, a mianowicie linka w stylu How to Sideload Apps onto an iPhone or iPad Without Jailbreaking.  XCode 7 pod tym względem to świetna po wielu latach wiadomość, no oczywiście dla tych co mają żródła aplikacji i chcą je wgrać na urządzenie -;)

czwartek, 21 stycznia 2016

Poznać lepiej wroga - Android 6: pierwsze wrażenia

Po konfiguracji systemu, odwiedzeniu wszystkich wbudowanych w niego aplikacji, a także kilku app-ek Microsoft i innych firm, nasuwa się kilka zdań podsumowania.  Generalnie zaskoczenia… nie było. Wszystko działa intuicyjnie dla mnie… użytkownika Windows 10 Mobile. Wszystko działa płynnie i szybko. Sprawny system aktualizacji systemu. Po zainstalowaniu dwóch lub trzech poprawek działam na 6.01.  Bardzo sprawne przełączanie języka w systemie (bez restartu).  Material Design faktycznie ładny i może się podobać, nie odczuwa się przerostu formy nad treścią mimo czasami nieco odmiennych koncepcji w porównaniu z Modern UI. Najlepsze oczywiście aplikacje systemowe. Aplikacje Microsoft-u na Androida z jednej strony czynią je podobnymi do postaci znanej z Windows, ale używają elementów interfejsu Androida i jakaś ich część stosuje większość zaleceń co do UI. Część z nich używa Material Design, w One Drive wykorzystano nawet pattern z guideline’a by animacyjnie rozwijać przyciski od głównego przycisku akcji. Aplikacje różnych innych firm, jeśli nie używają Material Design cóż… jednak się odcinają i czasami robią wręcz złe wrażenie.  Najbardziej trawię Material Design, bo… najbardziej przypomina mi Windows 10, nie ma tego wcześniejszego przerostu formy nad treścią. Material Design jest częściowo wspierany wstecz na urządzeniach ze starszym systemem niż Android 5, ale są to tylko style i część kontrolek. Animacje na przykład działają wyłącznie od Androida 5  i pisząc app-kę od założmy Kitkat trzeba warunkowo je włączać w kodzie zależności od wersji systemu. Jak się maja do tego wszelkiej maści frameworki?  W Xamarin np. się da korzystać w jakiś sposób z Material Design, na inne jeszcze pod tym względem nie patrzyłem.

P.S  Oczywiście niezależnie od tego nadal używam Windows 10 Mobile,  nie doszło tutaj do żadnej zamiany –;)

11102

Ukazał się nowy desktopowy build. Zainstalowałem. W tej wersji można mieć historię ostatnio przeglądanych stron pod przyciskami Back i Next, czego do tej pory w Edge’u nie było.

Ostatnie newsy związane z “10”-tką:

wtorek, 19 stycznia 2016

Poznać lepiej wroga - Android 6 preludium

Kilka dni temu wpadło mi do głowy kilka wisielczych zajawek, których pewne odbicia znajdą się w cyklu “Poznać lepiej wroga”.  Czemu wroga?  Ha, ha, bo niezależnie od wszystkiego i tak najbardziej znam i cenię sobie Windows 10 i wywodzę się z określonego środowiska, którym jest Windows i .NET. Jednak popularność urządzeń z Android i iOS-em jest naprawdę zastanawiajaca i nie można jej lekceważyć, co jest też zgodne z najnowszymi posunięciami Microsoft.

Na początek może wcielę się w rolę Android-ziarza (aczkolwiek na pewno nim na stałe nie zostanę). Zanim wezmę cokolwiek do ręki, zostałem już swiadomym użytkownikiem najnowszej wersji systemu i interfejsu użytkownika.  Pomogły mi w dwa linki:

Nie mogę być “prawdziwym” Android-ziarzem, bo każdą koncepcję UI, systemowe aplikacje i funkcjonalności porównywałem mimowolnie z Windows 10.  Nie jest to poznanie “tabula rasa”, patrzę na coś już przez przyzmat czegoś. Wszystko wydało mi się … generalnie znajome, aczkolwiek znalazłem pewną ilość koncecji i smaczków stricte typowych dla aktualnego Androida. Widać tu większą systematyzację w nazywaniu bytów UI, które w Windows nie są ponazywane (np. karty, chipsy). Na pewno przycisk z dominującą akcją jest tutaj mocno charakterystyczny, podobnie jak dość przemyślne animacje (np. związane z przyciskami i paskami).  Czy są cool, ułatwiają pracę, czy też są przerostem formy nad treścią?  Bliskie spotkanie trzeciego stopnia da mi odpowiedź.

sobota, 16 stycznia 2016

Dotyk i ruch w iOS

Jakiegoś specjalnego szału nie ma, generalnie koncepcje wydają się jakby skądś znajome. Więcej nieco mojej uwagi przykuły behaviory w podstawowym API np. do grawitacji, które mogą się podobać.

 

UITouch

właściwości

  • faza (UITouchPhaseBegan, UITouchPhaseMoved, UITouchPhaseStationary, UITouchPhaseEnded – wyszliśmy poza ekran, UITouchPhaseCancelled – anulowane przez system np. alert)
  • widok
  • główny promień
  • ilość naciśnięć
  • timestamp
  • tolerancja głównego promienia
  • okno
  • detektory gestów

metody

  • locationInView
  • previousLocationInView

UIEvent

właściwości

  • typ zdarzenia
  • podtyp
  • timestamp

metody

  • allTouches
  • touchesForView (view | window | gestureRecognizer)

Hit Testing

UIView

  • hitTest: touches withEvent: event
  • pointInside: point  withEvent: event

podklasy UIResponder:

  • UIApplication
  • UIWindow
  • UIViewController
  • UIView
  • UIControl

UIResponder - metody łańcucha reponderów:

  • canBecomeFirstResponder
  • becomeFirstResponder
  • canResignFirstResponder
  • resignFirstResponder
  • nextResponder

Application –> Window –>        Hit-test View –> Superview –> ViewController –> Window –> Application

zdarzenie dotykowe:  hit-test view ich nie przechwytuje

zdarzenia ruchu:  zdarzenia “Motion Began” i “Motion Ended” nie są zaimplementowane

zdarzenia zdalnej kontroli:  “Remote Control Received With Event” niezaimplementowane

komnikaty akcji:  nil celem metody akcji

komunikaty menu edycyjnego:  znajdują obiekt wykonujący metody

edycja tekstu:  pierwszy responder

 

Zdarzenia dotykowe

handlery w UIResponder:

  • touchesBegan:  touches  withEvent:  event
  • tochesMoved:  touches  withEvent:  event
  • touchesEnded:  touches  withEvent:  event
  • touchesCancelled:  touches  withEvent:  event

Wyłączenie zdarzeń dotykowych:

UIView

  • usunięcie z nadwidoku
  • ustawienie opcji interakcji usera na “NO” (domyślnie niektóre kontrolki tak mają np. UIImageView) zakładka boczna, sekcja View –> User Interaction Enabled
  • ustawienie propercji hidden na “YES”
  • ustawienie Opaque na “NO” i Alpha na zero

- (void)touchesBegan: (NSSet *)touches withEvent: (UIEvent *)event {

UITouch *touch = [[event allTouches] anyObject];

CGPoint touchLocation = [touch locationInView: _imageView];

if (CGRectContainsPoint(_imageView.bounds, touchLocation)) {

       _moveImageView = YES;

       _touchOffset = CGPointMake(touchLocation.x, touchLocation.y);

}

}

- (void) touchesMoved:  (NSSet *)touches withEvent: (UIEvent *) event  {

if (_movedImageView)  {

       UITouch *touch = [[event allTouches] anyObject];

       CGPoint touchLocation = [touch locationInView: _imageView];

       CGPoint offsetLocation = CGPointMake(touchLocation.x – _touchOffset.x, touchLocation.y – _touchOffset.y);

              CGPoint center = _imageView.center;

              center.x += offsetLocation.x;

              center.y += offsetLocation.y;

              _imageView.center = center;

}

}

- (void) touchesEnded:  (NSSet *) touches withEvent: (UIEvent *)event {

_moveImageView = NO;

}

- (void) touchesCancelled:  (NSSet *)touches withEvent: (UIEvent *)event {

[self touchesEnded: touches withEvent: event];

}

 

Gesture recognizers

  • Pan
  • Rotate
  • Tap
  • Pinch
  • LongPress  (“touch and hold” powiedziałbym językiem MS)
  • Custom

UIGestureRecognizer

  • UIView* view
  • UIGestureRecognizerState state
  • BOOL enabled
  • numberOfTouches
  • locationInView:  view
  • locationOfTouchIndex: touchIndex  inView: view

UIPanGestureRecognizer

  • NSUInteger minimumNumberOfTouches
  • NSUInteger maximumNumberOfTouches
  • velocityInView: view
  • translationInView: view
  • setTranslation: translation inView: view

gesture recognizer – stan:

  • discrete gestures (szybkie) np. tap
    • Recognized <-  Possible –> Failed
  • continuous gestures (więcej ruchu) np. pan, pinch, rotation
    • Possible –> Began –> Changed (x n)
    • Began, Changed –> Ended, Cancelled
    • Possible –> Failed

- (IBAction)pan: (UIPanGestureRecognizer*)pan  {  /*pan.state */ }

UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]  initWithTarget:self  action:@selector(myPanGesture:)];

[_imageView addGestureRecognizer:panGesture];

- (void)myPanGesture: (UIPanGestureRecognizer*)pan {

switch (pan.state) {

       can UIGestureRecognizerStateChanged:  {

              CGPoint offset = [pan  translationInView: pan.view];

               CGPoint center = pan.view.center;

               center.x += offset.x;

               center.y += offset.y;

               pan.view.center = center;

               [pan setTranslation: CGPointZero inView: pan.view];

         }

               break;

        default:

                break;

}

}

zoom-in & zoom-out:

- (IBAction)scaleView: (UIPinchGestureRecognizer*)pinch {

switch (pinch.state)  {

        case UIGestureRecognizerStateChanged:  {

                  CGAffineTransform currentTransform = pinch.view.superview.transform;

                  CGAffineTransform scale = CGAffineTransformScale(currentTransform, pinch.scale, pinch.scale)

                  pinch.view.superview.transform = scale;

                  pinch.scale = 1;

         }

                  break;

         default:

                  break;

}

}

wyszukiwarka kontrolek:  Pinch Gesture Recognizer, przeciągamy np na ImageView, przeciągamy w View Controller Scene na View Controller,  zaznaczamy scaleView:

obrót:

- (void)rotateView: (UIRotationGestureRecognizer*)rotate {

switch (rotate.state) {

        case UIGestureRecognizerStateChanged:  {

                  rotate.view.superview.transform = CGAffineTransformRotate(rotate.view.superview.transform, rotate.rotation);

                  rotate.rotation = 0;

        }

                  break;

        default:

                  break;

}

}

tap:

tapGesture.numberOfTapsRequired = 2;

- (void) resetView:  (UITapGestureRecognizer*) tap {

      if (tap.state == UIGestureRecognizerStateRecognized] {

              [UIView animateDuration: 0.25  animations: ^{

                      tap.view.superview.transform = CGAffineTransformIdentity;

                      tap.view.center = self.view.center;

             }]

       }

}

poprawka:  obrazek w storyboard: Editor –> Embed In View

UIGestureRecognizer Delegate

gestureRecognizer: recognizer  shouldRecognizeSimultaneouslyWithGestureRecognizer: otherRecognizer    /* return YES */

gestureRecognizerShouldBegin:  recognizer

gestureRecognizer: recognizer shouldReceiveTouch: touch

gestureRecognizer: recognizer shouldRequireFailureOfGestureRecognizer: otherRecognizer

gestureRecognizer: recognizer shouldRequiredToFailByGestureRecognizer: otherRecognizer

 

multi-touch

 

różne kontrolki  & gesture recognizers

  • jeden palec, jeden tap – np. button
  • jeden pan jednocześnie ze swipe – np. slider

mozna to nadpisać w klasie pochodnej

 

obsługa gestów z animacjami

 

Zdarzenia ruchu

Sensory:

  • żyroskop
  • akcelerometr
  • magnetometr

UIResponder:

  • motionBegan: motion withEvent: event
  • motionEnded:  motion  withEvent:  event
  • motionCancelled:  motion withEvent: event

- (void) motionEnded: (UIEventSubtype) motion  withEvent:  (UIEvent *) event  {

if (motion == UIEventSubtypeMotionShake) {

        …

}

}

Core Motion

  1. CMMotionManager
  2. CMAccelerometerData
  3. CMGyroData
  4. CMMagnetometerData
  5. CMDeviceMotion

behaviory (grawitacja, kolizje itp.)

//setup

self.motionManager = [[CMMotionManager alloc] init];

if (self.motionManager.deviceMotionAvailable) {

}

//stop

if (self.motionManager.deviceMotionActive) {

[self.motionManager.stopDeviceMotionUpdates]

}

self.motionManager = nil

 

CMDeviceMotion

  • userAcceleration  (G - przysp. ziemskie)
  • gravity (G)
  • rotationRate  (radiany / s)
  • magneticField   (uT)
  • attitude  (kąty Euler’a:  Roll, Pitch, Yaw  /  matryca rotacji / quaterniony)

Push

  • specjalizowane aplikacje
  • zbieranie danych

//setup

self.motionManager.deviceMotionUpdateInterval = 1.0 / 60.0;

[self.motionManager startDeviceMotionUpdatesToQueue: [NSOperationQueue mainQueue]  withHandler: ^(CMDeviceMotion *motion, NSError  *error)  {  … }

Pull

  • aktualizacja UI
  • gry
  • rozszerzona rzeczywistość

 

CADisplayLink – synchronizuje

//viewDidLoad

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget: self  selector: @selector(update:)];

[displayLink addToRunLoop: [NSRunLoop mainRunLoop] forMode: NSDefaultRunLoopMode];

- (void) update: (CADisplayLink*) displayLink  {

if (self.motionManager.deviceMotionActive) {

        CMDeviceMotion *motion = self.motionManager.deviceMotion;

        if (motion != nil)  {

                CGVector *newGravityVector = CGVectorMake(motion.gravity.x * 5, -motion.gravity.y * 5);

                self.gravityBehavior.gravityDirection = newGravityVector;

                [self.itemBehavior  addLinearVelocity: CGPointMake(motion.userAcceleration.x * 200,

                 motion.userAcceleration.y * 200)  forItem: self.imageView];

        }

}

}

//setup

[self.motionManager startDeviceMotionUpdates]  //zamiast startDeviceMotionUpdatesToQueue

środa, 13 stycznia 2016

11099

Pierwszy post w tym roku z liczbą w tytule. Tracydyjnie chodzi o numer nowego buildu Windows 10.  Dziś jest to build na PC, z poziomu którego już pisze.

Generalnie nic nowego w nim nie znajdziemy, ale zawsze to jakiś pozytywny sygnał w kierunku “Redstone”.

poniedziałek, 11 stycznia 2016

iOS & React Native

Nie znam React, to chyba React Native nie dla mnie…  Tak myślałem jakiś czas temu, ale dziś zapoznałem się w pewnej mierze z tym frameworkiem i muszę przyznać, że może się podobać. Upraszcza w znaczny sposób pewne standardowe rzeczy.  W dodatku zachęciło mnie to do samego Reacta, który - jeśli odpowiada w pewnym stopniu Native Reactowi - nie powinien wydawać się taki zły, a może być naprawdę wygodny i przyjemny. Jedynie samodzielne opakowywanie natywnego API w Objective-C nie wydaje się już do końca takie wygodne (brakuje mi czegoś na kształt automatycznej projekcji języka jak w WinRT).

 

No HTML, 100% native

debug w Chrome developer tools

stylowanie w składni bardzo podobnej do CSS

layout za pomocą FlexBox

narzędzia: Xcode, Homebrew

brew install node

brew install –HEAD watchman

brew install flow

npm install –g react-native-cli

react-native init GithubBrowser

otwieramy wygenerowany projekt Xcode

index.ios.js

‘use strict’;

var React = require(‘react-native’);

var {

AppRegistry,

StyleSheet,

Text,

View,

}    = React

var GithubBrowser = React.createClass({

render: function() {

       var msg = ‘abc’;

       return (

            <View style={styles.container}>

                    <Text style={styles.welcome}>

                              {msg}

                     </Text>

             </View>

      );

   }

});

var styles = StyleSheet.create({

        container:  {

             flex: 1,

             …

        },

        welcome: {

             fontSize: 20,

             textAlign:  ‘center’,

             margin:  10,

        },

       …

});

AppRegistry.registerComponent(‘GithubBrowser’, () => GithubBrowser);   

github Samples

npm –i

plik komponentu Login: 

var Text = React.Text;

… <Text style={{color: ‘white’}}>

module.exports =  Login;

inny view:

<Login>

 

<Image … source={require(‘image!Octocat’)}    //obrazki w Images.xcassets w Xcode

“styl”:  width, height - tylko liczby (nie piksele)

<TextInput … />

przycisk:

<TouchableHighlight … >

<Text …>

        Log in

</Text>

</TouchableHighlight>

 

ES6

class Login extends Component {

      constructor(props) {

             super(props);

             this.state = {

                     showProgress: false

             }

      }                

render() {

        return …

}

}

<TextInput  onChangeText={(text) =>this.setState({username: text)}

<TouchableHighlight onPress={this.onLoginPressed.bind(this)}

onLoginPressed() {

}

Progress

<ActivityIndicatorIOS  animating={this.state.showProgress}  size=”large” />

 

HTTP

fetch(‘http://…’)

.then((response)=> {

return response.json();

})

.then((results) => {

});

 

Autentykacja

var buffer = require(‘buffer’);

var b = new buffer.Buffer(‘hello’);

//b.toString(‘base64’);

 

---

var buffer = require(‘buffer’);

class AuthService {

login(creds, cb) {

     …

}

}

module.exports = new AuthService();

---

var authService = require(‘./AuthService’);

authService.login({

username: this.state.username,

password: this.state.password

}, (results)=> {

this.setState(Object.assign({

       showProgress: false

}, results));

});

 

przejście do innego widoku:

render: 

if(this.state.isLoggedIn)

return …

else

return …

//początkowy stan komponentu/widoku

getInitialState: function() {

return {

       isLoggedIn: false

};

}

 

dane:

AsyncStorage.setItem()

AsyncStorage.multiSet()

 

var AsyncStorage = require(‘react-native’).AsyncStorage;

AsyncStorage.multiSet([

[‘auth’, encodedAuth],

[‘user’, JSON.stringify(results]]

], …)

odczyt

npm install lodash –save

var _ = require(‘loadash’);

getAuthInfo(cb) {

AsyncStorage.multiGet([authKey, userKey], (err, val) => {

       if (err) {

                return cb(err);

       }

       if(!val) {

                return cb();

       }

       var zippedObj = _.zipObject(val);

       if (!zippedObj[authKey]) { return cb(); }

      var authInfo = {

              header: {

                     Authorization: ‘Basic ‘ + zippedObj[authKey]

              },

             user:  JSON.parse(zippedObj[userKey])

      }

      return cb(null, authInfo);

});

}

componentDidMount:  function() {

AuthService.getAuthInfo((err, authInfo)=> {

this.setState({

         …

         isLoggedIn: authInfo != null

})

})

},

 

Zakładki:

this.state = {

selectedTab: ‘feed’

}

<TabBarIOS  …>

<TabBarIOS.Item

          title=”Feed”

          selected={this.state.selectedTab == ‘feed’}

          icon={require(‘image!inbox’)}

          onPress={()=>this.setState({selectedTab: ‘feed’})}

     >

       <Text …>Tab 1</Text>

</TabBarIOS.Item>

</TabBarIOS>

 

listview

constructor:

var ds = new ListView.DataSource({

rowHasChanged:  (r1, r2) => r1 != r2

});

this.state = {

dataSource: ds.cloneWithRows([‘A’, ‘B’, ‘C’])

};

renderRow(rowData) {

return <Text …>

                   {rowData}

              </Text>

}

<ListView

dataSource={this.state.dataSource}

renderRow={this.renderRow.bind(this)} />

fetch(url, {

headers:  authInfo.header

})

.then((response) => response.json())

.then((responseData) => {

var feedItems = responseData.filter((ev) => ev.type == ‘PushEvent’);

this.setState({

        dataSource:   this.state.dataSource.cloneWithRows(feedItems)

});

 

<Image source={{uri:  rowData.actor.avatar_url}}

 

Navigator

<TouchableHighlight

      onPress={()=> this.pressRow(rowData)}

underlayColor=’#ddd’

>   //cały szablon itemu

</TouchableHighlight>

<TabBarIOS.Item  …>

<NavigatorIOS  …

          initialRoute={{

                 component:  Feed,

                  title:  ‘Feed’

           }}

pressRow(rowData) {

this.props.navigator.push({

        title:  ‘Push Event’,

        component:  PushPayload,

        passProps:  {

              pushEvent:  rowData

        }

});

}

 

<ListView

contentInset={{ top: –50 }}

 

Wywoływanie kodu Objective-C:

Encoding.h:

#include “RTCBridgeModule.h”

@interface Encoding: NSObject <RCTBridgeModule>

@end

Encoding.m:

#include “Encoding.h”

#include “RCTRootView.h”

      @implementation Encoding

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(base64Encode: (NSString*)str  callback: (RCTResponseSenderBlock)callback)

{

       NSData *nsdata = [str dataUsingEncoding:NSUTF8StringEncoding];

       NSString *base64Encoded = [nsdata base64EncodedStringWithOptions: 0];

       callback(@[base64Encoded]);

}

@end

JS:

var encoding = require(‘NativeModules’).Encoding;

encoding.base64Encode(authStr, (encodedAuth) => { … });

sobota, 9 stycznia 2016

iOS 8 & 9

Po Swifcie rzutem na taśmę przejrzałem z grubsza zmiany w ostatnich dwóch wersjach iOS 8 i 9. Generalnie zaskoczenia nie ma, a większość rzeczy jest jakby już skądś znanych.  Skąd?  Odnajduję kilka analogii, czasami wprost do tego, co można było zaobserwować w Windows 8.x czy 10. Ale teraz już pokolei pozwolę skomentować sobie, co najbardziej istotne.

  • aplikacje uniwersalne - jeden widok z warunkami na różne ekrany (Windows 10 też wprowadził taką możliwość, powiedziałbym, że warunki ma zrealizowane bardziej uniwersalnie)
  • dynamics frameworks - dzielone biblioteki między aplikacje (dopiero w wersji 8?)
  • view debugging - jak Live DOM w Visual Studio dla XAML (trzeba przyznać, że Apple poszło dalej i wizualizuje także warstwowo w przestrzeni)
  • iOS extensions - no proszę, mi się jednoznacznie kojarzą z kontraktami/rozszerzeniami Windows od 8.x, np. sharowanie, file picker  (komunikacja za pomocą linka też jest w aplikacjach Windows Store)
  • UIStackView - nareszcie coś na kształt w miarę normalnego StackPanel z XAML, aczkolwiek trochę urozmaicony
  • UILayoutGuide - pewne uproszczenia mocno niezjadliwego definiowania warunków layoutu w kodzie, aczkolwiek w dalszym ciągu nie widzę tutaj odpowiedniej prostoty
  • Multitasking - wyświetlanie kilku aplikacji na jednym ekranie, snapowanie?  znamy to z Windows 8!, aczkolwiek Apple urozmaicił to trybem over oraz pokazywaniem ramki video
  • “pliki nagłówkowe” dla Swift - widok podsumowujący “interfejs” klasy C# mamy od dawna w VS
  • App Thinning - pobieranie tylko potrzebnych zasobów dla danego urządzenia, kompilacja w Store, znamy to od Windows/Windows Phone 8.1
  • API do kontaktów - nareszcie jakieś sensowne API, mocno nawiązujące IMHO do hm… API z Windows
  • Search API - wyszukiwanie frazy powiązanej z aplikacją hm… również jakoś nie zaskakuje

 

iOS 8

w skrócie

  • uniwersalne aplikacje
  • więcej możliwości interakcji z innymi aplikacjami
  • ułatwienia w autentykacji
  • ułatwienia w płatnościach mobilnych (Apple Pay)
  • wsparcie dla Swift

XCode 6

wsparcie dla Swift prawie takie samo jak dla Objective-C (m.in brak refaktoringu)

testy - m.in Swift i asynchroniczność

iOS 8 Dynamic Frameworks

  • dzielone między wiele aplikacji
  • wcześniej tylko dzielenie źródeł lub statyczne biblioteki
  • szablon: iOS –> Framework & Library –> Cocoa Touch Framework

 

App Layout

iPhone 480 x 320

Retina:  iPhone4  960 x 320

iPhone 5, 5c, 5s   1136 x 640

iPhone 6  1334 x 750

iPhone 6+  2208 x 1242

Universal Storyboards w Xcode 6

stare:  iPhone.storyboard & iPad.storyboard

 

Live View Rendering

od Xcode 6 także własne widoki

  • trzeba dodać atrybut @IBDesignable i nadpisać metodę prepareForInterfaceBuilder
  • dodajemy atrybut @IBInspectable to propercji, które chcemy pokazać w inspektorze

 

Debugowanie

od Xcode 6 wsparcie dla View Debugging  (jak Live DOM w VS)

 

UIKit

Size Classes - “Responsive” design w iOS 8

kompatybilność wstecz z iOS 7 (dodawany dodatkowy kod)

SizeClass enum:   Unspecified (Any), Regular, Compact

w runtime jedna dla szerokości i jedna dla wysokości

deklaratywnie

Compact (W),  Compact(H):  compact

Regular (W), Compact (H): landscape

Compact (W), Regular (H): iphone portret

Regular (W), Regular (H) ipad

Widoki UIKit

  • wiele zostało rozszerzonych pod klasy rozmiarów
  • UIImage - działa automatycznie z klasami rozmiarów, gdy jest razem z obiektem UIImageAsset
  • UISplitViewController - podział jest kontrolowany przez horyzontalną klasę rozmiaru

obiekt UITraitCollection

  • klasy rozmiaru horyzontalne i wertykalne
  • wartość skalowania

+ Auto Layout

  • klasy rozmiaru integrują się z nim
  • warunki mogą bazować na klasie rozmiaru
  • Xcode 6:  wsparcie dla klas rozmiaru w warunkach

+ Views

  • widoki mogą być instalowane/usuwane na podstawie klasy Size

pasek na dole pod designerem:  w  h

na dole bocznego paska z właściwościami:  check installed (+ dodajemy warunek),  check przy installed decyduje o widoczności

Size Inspector:  Constraints   This Size Class  (ustawiamy względem klasy Size)

można też w kodzie:

view

public override func traitCollectionDidChange (previousTraitCollection: UITraitCollection?)  {

let c = self.constraints().filter {

        el –> Bool in

        if el.firstAttribute == NSLayoutAttribte.Width {

                  return true

        }

         return false

}

if let realC = c[0] as? NSLayoutConstraint {

        self.layer.cornerRadius = realC.constant / 2

}

}

 

iOS Extensions

  • wystawienie funkcjonalności jednej aplikacji dla innych
  • wcześniej tylko deep linki
  • iOS 8 (także OSX Yosemite)

Typy

  • Share (np. obrazek)
  • Actions (np. potrzebujemy aplikację do edycji obrazka, audio, …)
  • Photo Editing
  • File Provider
  • Document Provider
  • Today (własne UI do panelu Today w centrum notyfikacyjnym)

Extension Points

  • zdefiniowane w System Frameworks
  • kontrolują sposób uruchamiania oraz czy extension wyświetla View czy jest niewizualne

Model wykonywania  extension

  • ładowane w innym, zarządzanym przez system procesie niż aplikacja
  • ładowanie do Extension Host, każda instancja extension do osobnego hosta
  • proces extension jest izolowany, ale korzysta z grup aplikacji, może przekazywać klucze, domyślnego usera .itp

Extension Context

  • zamiast delegatu UIApplication, główny obiekt w extension
  • propercja view controllera extension

Today extension

  • view controller
  • ładować możliwie najszybciej
  • centrum notyfikacyjne ustawia ramki
  • rozmiary – autolayout lub za pomocą setPreferredContentSize
  • animacja rozmiarów za pomocą viewWillTransitionToSize
  • protokół NCWidgetProviding do aktualizacji zawartości

szablon: iOS –> Application Extension –> Today Extension

*.appex

Supporting Files:  Info.pblist  - definicja wspieranego protokołu URL

obsługa otwarcia aplikacji przez plik

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate {

func application(application: UIApplication, handleOpenURL url: NSURL) –> Bool {

         …

         return true;

}

}

view controller:

let url = “mytoday://newsstoryid”

if let realURL = NSURL(string: url) {

self.extensionContext !.openURL (realURL, completionHandler: nil)   //otwieramy naszą aplikację

}

 

Touch ID

KeyChain - specjalizowana baza używana głównie do przechowywania haseł

Access Control List

  • aplikacje w tej samej grupie dla keychain
  • zabezpiecza przed innymi aplikacjami

TouchID

dodany do iOS z iPhone 5s i iOS7

odciski palców jako dodatkowy mechanizm autentykacyjny

teraz może być użyty jako dodatkowa warstwa ochrony elementów keychain

Local Authentication – udowadnia, że użytkownik korzysta z aplikacji

 

 

iOS 9

  • UIKit
  • Xcode 7
  • Swift 2
  • nowe API

 

UIKit

  • UIStackview
  • UIKitDynamics
  • UILayoutGuide
  • Multitasking

UIStackView

automatycznie zakłada constrainty na ciągły układ elementów w kolumnie lub wierszu

orientacja, wyrównanie, sposób wypełnienia kontenera przez elementy (np. rozciągnięte), odstępy

let aStackview: UIStackView = UIStackView()

let aView = UIView()

aStackview.addArrangedSubview(aView)

aStackview.removeArrangedSubview(aView)

aView.removeFromSuperview()

automatyczna enkapsulacja zaznaczonych elementów po wybraniu Stackview

UIKitDynamics

od iOS7 (fizyka)

nowości:

UIFieldBehavior – wektor siły na element

rodzaje:

  • linear
  • radial
  • custom (np. siły z 2 stron)

let radialGrav:UIFieldBehavior = UIFieldBehavior.radialGravityFieldWithPosition(CGPointZero)

radialGrav.region = UIRegion.infiniteRegion()

radialGrav.strength = 0.5

radialGrav.falloff = 0.2

radialGrav.minimumRadius = 0.4

UIDynamicAnimator

ImageView

User Interaction Enabled

view controller:

private var animator: UIDynamicAnimator = UIDynamicAnimator()

override func viewDidLoad()  {

self.animator = UIDynamicAnimator(referenceView: self.vwContainer)

let imgTap = UITapGestureRecognizer(target: self, action: “enableNoise:”)

self.imageAvatar.AddGestureRecognizer(imgTap)

}

func enableNoise(tap: UITapGestureRecognizer)  {

let noiseField = UIFieldBehavior.noiseFieldWithSmoothness(5, animationSpeed: 1)

noiseField.addItem(self.imgAvatar)

       let boundsCollision = UICollisionBehavior(items:  [self.imgAvatar])

       boundsCollision.translatesReferenceBoundsIntoBoundary = true

       self.animator.addBehavior(noiseField)

self.animator.addBehavior(boundsCollision)

}

coś uwalniamy palcem i jak dotknie granicy, to się odbije i wróci do centrum

 

UILayoutGuide

Prostokątna przestrzeń, która wchodzi w interakcję z layoutem.

Definiowanie warunków na widok kontenera bez zaśmiecania hierarchii widoków.

  • layoutMarginGuide
  • readableContentGuide  (w orientacji pionej tekst u góry w kilku liniach, w orientacji poziomej w jednej linii centralnie)

let guideRef = self.view.layoutMarginsGuide

let readingContentRef = self.view.readableContentGuide

let container = UILayoutGuide()

self.view.addLayoutGuide(container)

self.view.removeLayoutGuide(container)

self.view.layoutGuides

self.view.layoutMarginsGuide.trailingAnchor

Factory do tworzenia obiektów NSLayoutConstraint przy użyciu fluentnego API. Nie trzeba używać inicjalizatora z dużą ilością parametrów.

let margins = view.layoutMarginsGuide

subview.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor).active = true

let label = UILabel()

label.translatesAutoresizingMaskIntoConstraints = false

 

Multitasking

kilka aplikacji na ekranie

3 rodzaje

  • Slide Over - z prawej strony przysnapowane okno nad aplikacją
  • Split View - podział ekranu na dwie aplikacje (osobne paski stanu, drugi ekran, wywołanie PiP, regularny;  używać auto layout, size classes, smart state transition)
  • Picture in Picture - mały ekran z video nad aplikacją (Audio & AirPlay, używać AVKit, AVFoundation, WebKit, MPMoviePlayerViewController, MPMoviePlayerController)

kompatybilność iPad

iPad Mini 2 & 3:  Slide Over, PiP (picture in picture)

iPad Air:  Slide Over, PiP

iPad Air 2:  Splitview, Slide Over, PiP

włączenie:

  • Launch Screen capability
  • wspierać wszystkie orientacje

wyłączenie:

  • UIRequiresFullscreen

wywoływana funkcja w aplikacji:

func applicationWillResignActive(application:  UIApplication)   [utrzymywać stan aplikacji]

 

Xcode 7

  • Storyboard References
  • wsparcie dla plików nagłówkowych
  • Playground Additions
  • App Thinning

Storyboard references

podział na małe, stawne instancje

let storyboard = UIStoryboard(name: “A_Storyboard”, bundle: NSBundle.mainBundle())

let viewController = storyboard.instantiateInitialViewController()

if let viewController = viewController  {

self.presentViewController(viewController, animated: true, completion: nil)

}

Storyboard Reference

przeciągamy na designer ze Storyboard

łączamy controller z referencją (Storyboard, Referenced ID, Bundle)

albo

zanaczamy kontroler, Editor –> Refactor to Storyboard…

nowy storyboard zawiera poprzednio wskazany kontroler

główny storyboard odnosi się do nowego storyboard zamiast wskazanego kontrolera

 

pliki nagłówkowe (Swift)

zalety plików .h bez ich minusów

plik Swift:  Navigate –> Jump to Generated Interface

 

App Thinning

  • Slices - na różnych urządzeniach nie są potrzebne te same assety (np. obrazki)
  • Bitcoin - AppStore odpowiedzialny za ostateczną kompilację (różne procesory armv6, armv7, armv7s, optymalizacje)
  • zasoby na żądanie - hostowane w AppStore i pobierane w razie potrzeby (NSBundleResourceRequest)

 

Nowe API

 

Contacts & ContactsUI

nowe API w miejsce starego opartego na C  (Address Book deprecated)

CNContact

  • nazwa
  • email
  • numer telefonu

CNLabeledValue - złożone typy

import Contacts

let homeEmail = CNLabeledValue(label: CNLabelHome, value:  “xxx@yyy.co”)

CNContactPickerViewController

wybór kontaktu:

view controller:  UIViewController, CNContactPickerDelegate

// import ContactsUI

let contactPicker = CNContactPickerViewController()

viewDidLoad:

contactPicker.delegate = self

contactPicker.displayedPropertyKeys = [CNContactPhoneNumbersKeys]

action:

self.presentViewController(contactPicker, animated: true, completion: nil)

func contactPicker(picker: CNContactPickerViewController, didSelectContactProperty contactProperty: CNContactProperty) {

let contact = contactProperty.contact

let phoneNumber = contactProperty.value as! CNPhoneNumber

//contact. givenName,  contact.familyName,   phoneNumber.stringValue

dodanie nowego kontaktu:

let contact = CNMutableContact()

contact.givenName = “xxx”

contact.familyName = “yyy”

let homeAddress = CNMutablePostalAddress()

homeAddress.street = “…”

contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: homeAddress)]

let store = CNContactStore()

let saveRequest = CNSaveResquest()

saveRequest.addContact(contact, toContainerWithIdentifier: nil)

do  {

try store.executeSaveRequest(saveRequest)

}

catch {

}

 

Search API

znajdowanie informacji wewnątrz aplikacji, nawet jeśli nie jest zainstalowana

  • NSUserActivity
  • Core Spotlight Framework
  • Web Markup

 

SFSafariViewController

  • standardowy interfejs do przeglądania web
  • funkcjonalności Safari (Reader, AutoFill, Fraudulent Website Detection, klucze, cookies, dane, blokowanie zawartości)

import SafariServices

let svc = SFSafariViewController(URL: NSURL(string: “https://xxx.yyy.com”))

self.presentViewController(svc, animated: true, completion: nil)

czwartek, 7 stycznia 2016

iOS - Swift 2.0

Jeśli szukasz w zimowy wieczór odskoczni od C# czy JavaScript, polecam zgłębienie aktualnej wersji Swift-a. Po zapoznaniu się z nią mam dwie główne uwagi. Wiedza ze Swift 1 generalnie w całości się przydaje (patrz: iOS, a co to jest - odcinek trzeci- Swift cz.1, iOS, a co to jest - odcinek czwarty- Swift cz.2iOS, a co to jest - odcinek piąty- Swift cz.3).  Dwójka ma jeszcze większy power (m.in nowa obsługa błędów, adaptatywny kod do wersji systemu, rozszerzenia protokołów, Open Source).

Nie widziałem jeszcze takiego języka jak Swift.  Mamy wygodę C# plus masę nowych wymyślnych udogodnień, lekkość JavaScript-u (a także znane z niego programowanie funkcyjne za pomocą klauzul), udogodnienia Objective-C, których nie było w innych językach i dużą konfigurowalność. Co prawda w C# dzięki Roslyn możemy dowolnie rozszerzać język, ale trzeba przyznać, że ileś pomysłów Swifta przydałoby się zinkorporować do przyszłej wersji C#. Wyliczenia i rozszerzenia biją wszystko! 

Dla równowagi dodam, że język ten w różnych miejscach może okazać się zachować inaczej niż byśmy się mogli spodziewać. Czasami odchodzi od dobrze znanych konstrukcji z C# czy Java (np. obsługa błędów). Automatyczne zliczanie referencji (w Objective-C od iOS 5 pojawiła się taka opcja) jest dość wygodne, jednak czasami możemy doprowadzić do wycieków pamięci, jak nie oznaczymy jakiejś składowej klasy odpowiednim słowem kluczowym (uważać m.in przy wzajemnej zależności jednej klasy od drugiej). Kontrola dostępu na poziomie pliku, a nie klasy (i brak trybu protected) wydaje się jakąś pomyłką z dawnej epoki (a może jest w tym właśnie jakaś głębsza myśl? czy jest sens coś zabezpieczać, jak jest w tym samym pliku?). Pewne kontrowersje może budzić zastosowanie etykiet do wychodzenia z pętli lub warunków, choć może to dawać swobodę przy zagnieżdżeniach.  Prowizorką wydaje się obejście na definiowanie protokołów generycznych.

 

Xcode 7

 

Operatory

kod adaptatywny

if #available(iOS 8.0, OSX 10.10, *) {

}

@available(iOS 8.0, OSX 10.10, *)

func f() { … }

niedozwolone:  a = b = c

operatory dopuszczające overflow

a &+ b     a &- b

a &* b     a  &/ b

operatory zakresu

1…3      1..<3

let i = 5

if 1..10 ~= i { … }

 

Typy

UTF8  // 16, 32, 64

Float80

typealias Unsigned = UInt16

 

let anInt = 10

let aDouble = 1.0

let x = aDouble + Double(anInt)

 

var str1: String = “abc”

var str2: NSString = “def”

str2 = str1

str1 = str2 as String

b.characters.count()

for ch in s { … }

b.utf8

s[s.startIndex]  - pierwszy znak

s[s.startIndex.successor()]  - drugi znak

s[s.endIndex.predecessor()]  - ostatni znak

s[advance(s.startIndex, 1)]  - drugi znak

s[advance(s.endIndex, –2)] - przedostatni znak

s.insert(“|”, atIndex: advance(s.startIndex, 4)) - na 5-tej pozycji

let range = 1 ..< 3

s.removeRange(range)

 

var x: Int?

x = Int(“xxx”)

if  x != nil  {

let num = x!  //wypakowanie, jeśli ma jakąś wartość

}

var y: Int!

y = Int(“yyy”)  //błąd jeśli nie ma wartości

 

if let a = optionalValue  { /* bezpieczne użycie a */ }

if let a = optionalValue,   //jeśli a ma wartość, wylicz b, jeśli b ma wartość  sprawdź a < b, jeśli a < b policz c, jeśli c ma wartość

b = bar()  where a < b,

let c = cow()   { …}

 

var array = [String]()

var array2 = [String](count:3, repeatedValue:””)

array.removeLast()

var twoD: [[String]] = [  [“a”, “b”, “c”],  [“d”, “e”, “f”] ]

twoD[0] = [“A”, “B”, “C”]

twoD[0][0] = “x”

 

var mySet: Set<String> = [“A”, “B”, “C”]

var c = Set<String>( [“A”, “B”, “C”])

insert, remove, removeAll, isEmpty, count, contains, isDisjointWith, isSupersetOf, isSubSetOf, isStrictSupersetOf, isStrictSubsetOf

a.exclusiveOr(b)    a.intersect(b)     a.substract(b)      a.union(b)

a.exlusiveOrInPlace(b)   itd.

 

var httpStatus:  [Int: String] = [:]   //słownik

for (code, message) in httpStatus  {  … }

 

Klasy i struktury

class Circle {

var center = Point()

var radius = 0.0

init(c: Point, r: Point) {

      center = c

      radius = r

}

//nie można nadpisać

final func moveTo(center: Point) {

      self.center = center

}

static var offset = getDefault()

class func offset(here: Point) { … }  //static nie pozwala nadpisać, class pozwala

}

let c1 = Circle()

let c2 = Circle(c: Point(), r: 5.0)

 

struct Circle {

mutating func moveTo(center: Point)  { … }  //modyfikacja wartości

mutating func reset() {  self = Circle() }

}

 

class Node {

var optDict:  [String:String]?

var next: Node?

func successor() –> Node?  { return next }

}

if let a = node.next?.optDict?[“k”] ?.isEmpty { … }

 

Tuples

let http404Error = (404, “Not Found”)

let (status, desc) = http404Error

print(“\(status):  \(desc)”)

let (status, _)  = http404Error  //interesuje nas tylko pierwsze pole

func fetch() –> (code: Int, desc: String) {

return (200, “OK”)

}

 

Wyliczenia

enum CompassPoint {

case North,

case South,

case East, West

}

switch direction {

case .East:    …

default:  …

}

mega konstrukcja!

enum PostalCode {

case US(Int, Int)

case UK(String)

case CA(code: String)

}

var p1 = PostalCode.US(94707, 2625)

switch s {

case .UK (let s):  …

case .US (let loc, var route):  …

case .CA:  break;

}

let x = CompassPoint.South.rawValue

if  let c = CompassPoint(rawValue: 5)  {  … }

enum Dimension {

case DISTANCE (Int)

init (distance: Int) { self = DISTANCE(distance + 100)}

func value() –> Int {

       switch self  {

              case .DISTANCE  (let value) : return value

       }

}

}

enum ConnectionState {

case closed, opening, open, closing

mutating func next()  {

       switch self  {

               case closed:  self = opening

               case opening:  self = open

               …

        }

}

}

 

Kontrola przepływu

for element in list  {  … }

for i in 0 ..< list.count  {

print(“\(list[i])”)

}

for var i=0; i < list.count; ++i  { 

}

for _ in 1…5  {

print(“hello”)

}

forever

for  ;;  {

print(“hello”)

}

repeat  {

}   while condition

break i continue

whileLabel:  while c1 {

if c2 { continue whileLabel }

if  c3 { break whileLabel }

}

outerLabel:  if c1 {

innerLabel:  if c2 {

         if c3  { break outerLabel }

         if c4   { break innerLabel }

}

}

let ch: Character = “e”

switch ch {

case “a”, “e”, “i”:   …

case  “a”…”z”:  fallthrough

default:  …

}

let str = “wilk”

switch str {

case “anoda” …”antena”

          …

case “kajman”…”prosto”

         …

default:

         …

}

let p = (1.0, 2.0)

switch p {

case (let x, 2.0):  …

case (1.0, let y):  …

case let(x,y) where x > 0.0 && y > 0.0:  …

case let(x,y):  …  //lub default:

}

switch rgbaColor {

case (1.0, 1.0, 1.0, 1.0:  …

case let (r, g, b, 1.0) where r==g && g ==b: …

case (0.0, 0.5…1.0, let b, _):  …

default: break

}

let r = 1.0, g = 1.0, b = 1.0, a = 1.0

switch (r, g, b, a) {

//tak samo jak wcześniej przy tuple

}

enum Status {

case OK(status: Int)

case Error(code: Int, message: String)

}

switch status {

case .OK(0):  …

case .Error(0, _): …

case .Error(1..<100, _): …

case .Error(let code, let msg): …

}

enum może mieć w swoim paametrze inny enum, obsługuje to także switch

switch connection {

case .Down(.Error(0,_)):  …

case .Down(.Error(let status, _)) where status==1 || status > 5:  …

case .Down:  …

}

var opt: Int? = 10

switch opt {

case let val?  where val > 0:  …

case nil:  …

}

if case let .UK(val) = place1 { …}

var array: [Int?] = [1, nil, 3]

for case let n? in array where n < 3 {  … }

 

obsługa błędów

assert(warunek, “Message”)

assertionFailure(“Message”)

precondition(warunek, “Message”)   //zawsze aktywne, także w wersji produkcyjnej

preconditionFailure(“Message”)

fatalError(“Message”)  //zatrzymuje program

“wyjątki”

class MyFile {

enum Error: ErrorType { case NoSuchFile(String), NotOpen }

init ( _ path: String) throws {

         if !MyFile.exists(path)  {

                  throw Error.NoSuchFile(path)

          }

         func readLine() throws –> String {

                  if !isOpen {  throw Error.NotOpen }

                  return “xxx”

         }

}

}

assert(MyFile.exists(“fff”))

let file = try!  MyFile(“fff”)   //każda metoda mogąca wyrzucać poprzedzona try!, nie ma typowego bloku try i catch

defer { file.close() }  //blok wywoływany bez względu na wszystko

blok do catch

do {

let file2 = try MyFile(“yyy”)

defer { file2.close() }

try file2.readLine()

}

catch let MyFile.Error.NoSuchFile(path) where path==”/x” {    //catch podobny do case w switch

}

catch {  … }

 

var optX: Int? = 10, optY: Int? = nil

if let x=optX, max=optY where 0 < x && x < max  {

let op = doSomething(x)

if let o = op

        something(o)

inaczej:  guard (wykonywanie jak coś pójdzie nie tak)

guard let x=opX, max=optY where 0 < x && x < max  else { return }

let op = doSomething(x)

guard let o = op else { return }

doSomething(o)

zmienna w bloku if jest widoczna tylko w tym bloku, nie jest widoczna w else ani na zewnątrz

zmienna w guard jest widoczna na zewnątrz

 

Funkcje i klauzule

func noReturn() –> () { … }

func printName(given first: String, family last: String) {

print(“\(first) \(last)”)

}

printName(given: “Alibaba”, family: “Matheo”)

func printName( _ first: String, _ last: String) { …}

printName(“Alibaba”, “Matheo”)  //przy opuszczeniu zewnętrznych _ w wyołaniu można w ogóle pominąć nazwy

domyślnie zewnętrzna nazwa pierwszego argumentu jest _, w kolejnym jest to jego wewnętrzna nazwa

przeładowanie funkcji (typ parametru, zestaw parametru, zwracany rodzaj, przy podaniu zewnętrznych nazw parametrów przeładowania dla różnych nazw parametru tego samego typu)

parametr z domyślną wartością

func concatenate( strings: [String], delimiter: String = “,”) –> String { … }

func sub(a: Int = 0, b:Int = 0) –> Int { … }

sub()

sub(a: 10)

sub(b: 10)

func sub(a a: Int = 0, b: Int = 0) –> Int { … }

sub(a: 20, b: 10)

sub(b: 10, a : 20)  //w tym przypadku można podać argumenty w innej kolejności

dowolna ilość parametrów

func sum(numbers: Int…) –>Int {

for number in numbers {

      …

}

}

sum (1, 2, 3, 4)

przekazanie przez referencje

func swap( inout a: Int, inout _b: Int ) {

let t = a

a = b

b = t

}

var left = “L”

var right = “R”

swap(&left, &right)

 

funkcja jest obiektem

func hello() { print(“hello”) }

var talk: () –> () = hello

talk()

funkcja może być zdefiniowana wewnątrz innej funkcji i zwracana jako wynik

func F(value: Int) –> (Int) –>(Int) {

func up (input: Int) –> Int { return input + 1 }

func down (input: Int) –> Int { return input - 1 }

return (value < 0 ? up : down)

}

 

klauzule / funkcje lambda

let ref = { (x: Int) –> Double in return Double(x) }

let ref: (Int) –> Double = { Double($0) } // nie musimy w definicji klauzuli powtarzać sygnatury, bo jest już wnioskowana z typu ref

var names = [ “Borixon”, “Alibaba”, “Popek” ]

names.sort( { (s1: String, s2: String) –> Bool in return s1 < s2 } )

names.sort() { $0 < $1 }  //gdy jedynym parametrem jest klauzula można ją wyciągnąć na zewnątrz  //trailing closure

names.sort { $0 < $1 }

func adder(increment: Int) –> (Int) –> Int {

return { increment + $0 }

}

var add = adder(10)

add(20)  //30

func getIncrementers(amount: Int) –> (increment: ()->Int, decrement: ()->Int) {

var total = 0

return ( { total += amount; return total}, {total –= amount; return total} )

}

let (up5, down5) = getIncrementers(5)

up5()  //5

up5()  //10

let (up1, down1) = getIncrementers(1)

up5()  //5

up1() // 1

up5() // 10

let str: String = { … }()  //wywołanie klauzuli w miejscu jej definicji

 

Propecje

field/member == stored property

property (get/set) == computed property

class Person {

var firstName = “Alibaba”

var fullName: String {

          set {

                …  newValue

          }

          get {

               return …

          }

}

//read-only

var email: String {   return … }

}

nadpisywanie

class Employee: Person {

override var fullName: String { get { … }  set { … } }

override var email: String { … }

override var firstName: String { set{…} get{…} }  //nadpisanie propercji stored propercją computed!

override var lastName: String {

       willSet(newVal) { … }

       didSet(oldVal) { …;  lastName = lastName.uppercaseString } 

//można zmieniać propercję w obserwatorze didSet, ale w klasie pochodnej prowadzi do wpadniecia w nieskończoną pętlę wywołań obserwatorów

}

}

w klasie bazowej takiego problemu nie będzie

class Person {

var lastName: String = “Alibaba” {

         willSet(newVal) { … }

        didSet(oldVal) { …; lastName = lastName.uppercaseString }

}

}

 

w klasie pochodnej inicjalizatory convenience mogą wywoływać tylko inicjalizator designated lub inne convenience z tej samej klasy

jeśli zdefiniujemy wszystkie inicjalizatory designated w klasie pochodnej to możemy użyć inicjalizatora convenience z bazowej

inicjalizator, który może zawieść

struct Failable {

init?(_ x: String) {

         if x.isEmpty {

                  return nil

         }

}

}

if let f = Failable(“xxx”) { … }

wymuszenie konieczności nadpisania inicjalizatora w klasie pochodnej

class Super {

required init() { … }

}

class Sub: Super {

required init() {

       …

}

}

 

dostęp na poziomie pliku, nie klasy

public

internal

private  (w tym samym pliku)

nie ma protected

generalnie w pliku powinna być jedna klasa

wszystkie pola w klasie i metody pomocnicze powinny być prywatne

public class Super {

public func doSomething() { }

}

public class Sub: Super {

override public func doSomething() { … }  //nadpisująca metoda nie może zmienić public na private, ale private na public już tak !!! (pod warunkiem że typy parametrów są publiczne)

}

w klasie można umieścić definicje innej klasy

private (set)

public var x: Int { set { … } get { return 0 } }

 

rzutowanie

is  as!  as?

0.0 as Double

AnyObject

Any

 

generyki - przy wywołaniu metody nie podajemy typów w <>

 

rozszerzenia

extension String {

var length: Int { return self.characters.count }

}

 

c === self   //ten sam obiekt

 

class HTMLElement {

lazy var asHTML: () –>String = {

         [unowned self] in

         return self.content == nil

               ? “<\(self.tag) />”

               : “<\(self.tag)>\(self.content!)</\(self.tag)>”

}

}

 

Protokoły

protocol P {

func representation(asType: Format) –> String

init(asType: Format, contents: String)

}

class E: P {

func representation(asType: Format)  -> String  {

      …

}

required init(asType: Format, contents: String)  {   //przy implementacji inicjalizatora wymaganego przez interfejs dodajemy required

      …

}

 

protocol Cacheable {

}

class CacheableEmployee: Employee, Cacheable {   //Employee: Representable

}

func f(x: protocol<Representable, Cacheable>) {

}

 

protocol Cacheable:  Representable {

static var versionID:  Double { get set }

            var objectID:  String { get }

func flush() –> String

mutating func load(flushId:  String)

static func f(a: Double, b: Double)

}

class CacheableEmployee: Employee, Cacheable {

static var versionID = 1.0  //nie musi być get i set

required init() {

        super.init(asType: Format.JSON, contents:””)

}

required init(asType: Format, contents: String) {

       super.init(asType: asType, conents: contents)

}

func load(flushId: String) { … }   //nie ma mutating w class

static  func f(a: Double, b:  Double)  { … }

}

 

class C<T: Cloneable> { … }

class C<T where T: Cloneable, T: Representable> { … }

 

protocol SupportsReplace {

func replaceWith(other: Self)  //Self – klasa implementująca ten interfejs

}

class ReplaceableEmployee:  Employee, SupportsReplace {

func replaceWith(other:  ReplaceableEmployee) {

         …

}

}

w Swift będzie nielegalna metoda:

func overwrite(original: SupportsReplace, with: SupportsReplace) {

original.replaceWith(with)

}

ale można tak:

func overwrite<T: SupportsReplace>(original: T, with: T) {

original.replaceWith(with)

}

 

protocol Cloneable: class {

func clone() –> Self

}

final class CloneableEmployee:  Employee, Cloneable {

// func clone() –> Self {

//        return self

//}

func clone() –> CloneableEmployee {

      return CloneableEmployee(“xxx”)

}

}

protocol Cloneable: class {

func clone() –> Cloneable

}

class CloneableEmployee: Employee, Cloneable {

func clone() –> Cloneable {

        return CloneableEmployee(“xxx”)

}

}

 

nie ma protokołów generycznych, ale można osiągnąć coś podobnego za pomocą typealias

protocol Container {

typealias T

mutating func append(item: T)

}

class Queue: Container {

typealias T = String  //można pominąć

func append(item: String) { … }

}

func allItemsMatch <C1: Container, C2: Container where C1.T==C2.T, C1.T: Equatable> (first: C1, second: C2) –> Bool {  … }

 

@objc enum ObjcEnum: Int { case X, Y }

@objc protocol ObjcProtocol { // AnyObject, ObjcEnum

func f (x: Int)

func >= (left: Int, right: Int) –> Bool  //przeładowanie operatora

}

class ObjcSubclass: ObjcProtocol {

@objc func f(x: Int ){}

}

class ObjcSubclass: NSObject, ObjcProtocol {

func f(x: Int) {}

}

 

@objc protocol P {

optional var optVar: Int   { get }

optional func optMethod() –> Int

func f()

}

class MyClass: P {

@objc var optVar: Int  { … }   //nie musi być

@objc func optMethod()->Int { … }  //nie musi być

}

opt.optMethod?()

opt.optVar

 

protocol TextRepresentable {

func asText() –> String

}

class Person {

}

extension Person: TextRepresentable {    //rozszerzenie dodające implementację protokołu

func asText() –> String { return “Alibaba” }

}

 

rozszerzenie protokołu

public protocol MyCollection {

typealias T

init( _ args: T… )

func elements() –> [T]

}

public class IntCollection: MyCollection { … }

let obj = IntCollection(1,2,3)

public extension MyCollection where T: SignedIntegerType {

public func sum() –> T { … }  // jeśli klasa sama nie zaimplementuje tej metody, zostanie użyta domyślna implementacja z rozszerzenia

}

obj.sum()

 

Customizacja

public class Employee { … }

extension Employee: Equatable {}  //Equatable wymaga publicznej funkcji ==

public func == (lhs:Employee, rhs: Employee) –> Bool {

return lhs.name == rhs.name

}

podobnie porównania z rozszerzeniem Comparable

do słownika:

extension Employee: Hashable {

public var hashValue: Int { return name.hashValue }

}

 

inicjalizacja stringa liczbą

let number:String = 1234

extension String: IntegerLiteralConvertible {

public init(integerLiteral value: Int) {

          self = “\(value)”

}

}

var s = “0123456789”

s[2]

s[1…3]

s[2] = “b”

s[7..9] = “XYZ”

public extension String {

public subscript(index: Int) –> String {

        get {

                return self.substringWithRange(

                        Range(start: advance(self.startIndex, index),

                                      end: advance(self.startIndex, index+1))

                )

        }

        set {

               self.replaceRange(

                      Range(start: advance(self.startIndex, index),

                       end: advance(self.startIndex, index+1)),

                      with: newValue

               )

        }

}

public substrict(r:Range<Int>) –>String {

        get {

                return self.substringWithRange(

                         Range(start: advance(self.startIndex, r.startIndex),

                                        end:  advance(self.startIndex, r.endIndex)))

        }

        set {

             //podobnie jak set wyżej, tylko zakresy z tego gettera

        }

}

}

wyrażenia regularne

let isALetter = try! Regex(“[a-z]+”)

isALetter.matches(“abcdef”)

public class Regex {

private let pattern: String

private let expression: NSRegularExpression

private let noOptions = NSRegularExpressionOptions(rawValue:0)

public init(_ pattern: String) throws {

        self.pattern = pattern

       try! expression = NSRegularExpression(pattern: pattern, options: noOptions)

}

public func matches(input: String) –> Bool {

      let result = expression.matchesInString(

            input, options: NSMatchingOptions(rawValue:0), range: NSMakeRange(0, input.count))

     return result.count > 0

}

}

użycie w case:

case try! Regex(“[0-9]+”)

public ~= (inCase: Regex, inSwitch: String) –> Bool {

         return inCase.matches(inSwitch)

}

można ładniej:

case ~”[0-9]+”:

prefix operator ~ {}   //zupełnie nowy operator

public prefix func ~ (operand: String) –> Regex {

do { return try Regex(operand) }

catch {

          preconditionFailure(“xxx”)

}

}

isALetter ~ “abc”

“abc” ~ isALetter

isALetter !~ “012”

“012” !~ isALetter

“abc” ~~”[a-z]+”

infix operator ~ {associativity left precedence 130}

infix operator !~ {associativity left precendence 130}

public func ~ (lhs: Regex, rhs: String) –> Bool { return lhs ~= rhs }

public func ~ (lhs: String, rhs: Regex) –> Bool { return rhs ~=lhs }

własny inicjalizator

let q:PriorityQueue<Int, Int> = [ (item:0, priority:0), (1,1) ]

public class PriorityQueue <I, P:Comparable>:  ArrayLiteralConvertible {

public init() {

}

public required init(arrayLiteral elements: (item: I, priority:P)…) {

         for element in elements  {

                 append(element.item, priority: element.priority)

         }

}

}

jeszcze ładniej:

var q:PriorityQueue<Int, Int> = [ 2:2, 3:3 ]

public class PriorityQueue <I, P:Comparable>: ArrayLiteralConvertible, DictionaryLiteralConvertible {

public required init( doctionaryLiteral elements: (I,P)… ) {

          for (item, priority) in elements {  … }        

}

}

własna iteracja

q4 = [ 0:0, 1:1, 2:2 ]

for item in q4 { … }

extension PriorityQueue: SequenceType {

public func generate() –> _PriorityQueueIterator<I> {

        return _PriorityQueueIterator<I>(items: ….)  

}

}

public struct _PriorityQueueIterator <I>: GeneratorType {

private let items: [I]

private var index = 0

      init( items: [I] ) {

      self.items = items

}

public mutating func next() –> I? {

       return index >= items.count ? nil : items[index++]

}

}

poniedziałek, 4 stycznia 2016

iOS - Swift w aplikacji

Eklektycznej wędrówki po platformach, językach i technologiach ciąg dalszy. Dziś padło na Swift. Jakiś czas temu zwróciłem uwagę na ten język. Jeśli chodzi o aplikacje iOS w tym języku, to można streścić to w jednym zdaniu. Generalnie w XCode jest wszystko tak samo jak podczas pisania w Objective-C, operujemy na tym samym z grubsza API, tylko kod piszemy w Swift –;)  A to jest nowocześniej i krócej.

 

projekt aplikacji w XCode w Swift – layout tworzymy analogicznie jak wcześniej

import UIKit

class ViewController: UITableViewController {

let array = [“item1”, “item2”, “item3”]

override func viewDidLoad() {

       super.viewDidLoad()

       tableView.rowHeight = 70

       tableView.backgroundView = UIImageView(image: UIImage(named: “breads”))

}

override func viewDidAppear(animated: Bool) {

      super.viewDidAppear(animated)

      navigationController?.navigationBar.alpha = 0.5

}

override func tableView(tableView: UITableView, numberOfRowsInSection: Int, section: Int) –> Int {

       return array.count;

}

}

outlety w designerze tworzy się podobnie jak wcześniej

class XController: UIViewController {

@IBOutlet var titleText: UITextField!

}

 

model

nowsza wersja Swift:

let cell = sender as! customcell

class Recipe: NSObject {

var title: String?

init (title: String) {

      self.title = title

}

override init() {

     super.init()

}

}

class RecipeManager: NSObject {

static var recipes = [Recipe]()

class func AddRecipe(title: String) {

       var r = Recipe(title: title)

       recipes.append(r)

}

}

 

tworzenie akcji za pomocą designera podobnie jak wcześniej

@IBAction func doneButton_click(sender: AnyObject) {

}

obsługa zmian wartości

NSNotificationCenter.defaultCenter().addObserver(self, selector: “textTitleDidChange”, name: UITextFieldTextDidChangeNotification, object: nil)

func textTitleDidChange() {

}

dodawanie animacji do przycisku

@IBOutlet var activityIndicator: UIActivityIndicatorView!

activityIndicator.startAnimating()

let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC)))

dispatch_after(time,

dispatch_get_main_queue()) {

        self.activityIndicator.stopAnimating()

}

usuwanie wiersza z animacją

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

if (editingStyle == .Delete) {

       …

       tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

}

}

persystencja danych

var defaults = NSUserDefaults.standardUserDefaults()

var myobject = defaults.objectForKey(“myobject”) as? String

defaults.setObject(“mystring”, forKey: “myobject”)

class Recipe: …, NSCoding {

required init(coding aDecoder: NSCoder) {

       if let titleDecoded = aDecoder.decodeObjectForKey(“title”) as? String {

              title = titleDecoded

       }

}

}

piątek, 1 stycznia 2016

Android - binding

Nowy rok zaczniemy małą dygresją dotyczącą data bindingu w Android.  Można powiedzieć, że powstało coś próbujące wzorować się na kompilowanym data bindingu w XAML. W porównaniu do tego, co było (zupełny brak bindingu) znaczący postęp, jednak w porównaniu do bindingu znanego z XAML (czy choćby Angulara 1 czy 2) wciąż bardziej złożony w użyciu… W każdym razie cieszy wchodzenie podejścia MVVM czy wzorca obserwatora w miejsce mediatora.

 

RoboBinding – framework MVVM dla Android

http://robobinding.github.io/RoboBinding/about.html

Butter Knife - biblioteka view “injection” dla Android

http://jakewharton.github.io/butterknife/

Android Annotations

http://androidannotations.org

Data Binding (Google) – generacja kodu (analiza pliku z layoutem, stworzenie klasy bindingu, stworzenie metod bindingu w pliku klasy),  gradle plugin

wsparcie w Android Studio:

1.3 +  (141.2117773) [Preferences –> Appearance & Behavior –> System Settings –> Updates]

 

prosty data binding

Android Plugin for Gradle 1.3.0 +

Data Binding Plugin 1.0-rc1 +

https://developer.android.com/tools/data-binding/guide.html#build_environment

Android SDK

Android Support Repository

Android Support Library

[Preferences –> Apperance & Behavior –> System Settings –> Android SDK]

projekt:

build.gradle

dependencies

classpath ‘com.android.tools.build:gradle:1.3.0’

classpath ‘com.android.databinding.dataBinder:1.0-rc1’

app/build.gradle

dodajemy apply plugin: ‘com.android.databinding’

w dependencies dodajemy  apt ‘com.android.databinding:compiler:1.0-rc1’

 

<layout>

<data>

         <variable

                  name=”dataSource”

                  type=”com.xxx.yyy.DataSource”/>        

</data>

<LinearLayout …>

       <TextView

                   …

                  android:text=”@{dataSource.message}”/>   // public String getMessage();  public String message(); 

                  // public String message;   inaczej błąd kompilacji

</LinearLayout>

</layout>

MainActivity (extends AppCompatActivity) onCreate

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);  //zamiast  setContentView

//ActivityMainBinding na podstawie activity_main.xml

binding.setDataSource(dataSource);

 

view

View view = inflater.inflate(R.layout.fragment_data, root, false);

FragmentDataBinding binding = DataBindingUtil.bind(view);  //lub FragmentDataBinding.bind(view);

FragmentDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_data, root, false);

FragmentDataBinding binding = FragmentDataBinding.inflate(inflater, root, false);

FragmentDataBinding binding = DataBindingUtil.getBinding(view);

 

bindowanie fragmentów

podobnie w layoucie

DataFragment extends Fragment, onCreateView:

FragmentDataBinding binding = FragmentDataBinding.inflate(inflater, container, false);

binding.setDataSource(dataSource);  //nazwa metody od DataSource

return binding.getRoot();

 

bindowanie załączonego layoutu

<include layout=”@layout/include_view

bind:dataSource=”@{dataSource}”/>

include_view.xml:

<layout>

<data>

          <variable

                    name=”dataSource”

                    type=”xxx.yyy.DataSource”/>

</data>

…  //binding TextView tak samo

</layout>

 

bindowanie załączonego własnego layoutu

w XML podobnie

 

View view = inflater.inflate(R.layout.data_view, root, false);

DataViewBinding binding = DataBindingUtil.bind(view);  //DataViewBinding.bind(view);

DataView extends FrameLayout

onFinishInflate:

binding = DataViewBinding.bind(this);

onAttachedToWindow:

binding.setDataSource(dataSource);

<include layout=”@layout/data_view”/>

 

bindowanie powtarzanych elementów

RecyclerView

projekt: dependencies:

+ compile ‘com.android.support.recyclerview-v7:22.2.1’

aktywność:

<android.support.v7.widget.RecyclerView android:id=”@+id/list”  …/>

item_view.xml – layout itemu, podobnie jak wcześniej

aktywność – klasa: onCreate

binding.list.setLayoutManager(new LinearLayoutManager(this));

binding.list.setAdapter(new DataSourceAdapter(getLayoutInflater()));

private class DataSourceAdapter extends RecyclerView.Adapter<ViewHolder> {

private LayoutInflater layoutInflater;

public DataSourceAdapter(LayoutInflater layoutInflater) {         

           this.layoutInflater = layoutInflater;

}

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

         ItemViewBinding binding = ItemViewBinding.inflate(layoutInflater, parent, false);

         return new ViewHolder(binding.getRoot());

}

@Override

public void onBindViewHolder(ViewHolder holder, int position) {

         …

         ItemViewBinding binding = DataBindingUtil.getBinding(holder.itemView);

         binding.setDataSource(dataSource);

}

@Override

public int getItemCount() {

        return 0;

}

}

private class ViewHolder extends RecyclerView.ViewHolder {

public ViewHolder(View itemView) {

          super(itemView);

}

}

Możliwość własnej klasy (widoku)

Wiele layoutów

 

stategie bindingu:

Inflate and Bind

Late Bind

Binding Reference

 

<data>

<variable name=”name” type=”String” />  //do bindowania atrybutów w layoucie

 

pobieranie / ustawianie wartości

layoutBinding.getName();

layoutBinding.setName(String name);

domyślne wartości są ustawiane w runtime  np. null dla name

 

<import type=”com.xxx.StringUtils” />

<TextView android:text=@{StringUtils.upper(name)}/>

 

<import type=”com.xxx.DataSource”/>

<import alias=”SqlDataSource” type=”javax.sql.DataSource”/>

<variable name=”data” type=”DataSource”/>

<variable name=”sql” type=”SqlDataSource”/>

 

<data class=”” />  //domyślny namespace  {module}.databinding, możliwość zmiany nazwy generowanej klasy bindingu i docelowego pakietu

<data class=”com.ddd.fff.eee.MainActivityBinding”/>

 

Layout Expression Language

odczyt i ustawianie propercji

bez new i super

wyliczanie i konwersja wartości

formatowany string

obsługa kolekcji

pobieranie elementu kolekcji na podstawie klucza lub indeksu

warunkowa logika

 

w strings.xml w <resources> m.in formatowane stringi z % i kolekcja string-array

android:visibility=”@{item.isSpecial ? View.VISIBLE : View:INVISIBLE}”

android:text=”@{@stringArray/sizes[item.index]}”

android:text=”@{item.map[‘xxx’]}”/>

android:text=”@{@string/price(item.map[‘price’])}”

 

czasami referencje do zasobów w wyrażeniach bindingu różnią się od typowego użycia

http://developer.android.com/tools/data-binding/guide.html

 

Własny i dynamiczny binding

Custom Binding Adapter

Default Binding Adapter – np. nie cachuje obrazków

Custom Event Listeners

Custom Event Binding

bindowanie typu listenera w layoucie

android:onClick=”@{listeners}”

lub

binding.setListeners(listeners)

 

Dynamiczny binding

bindowany typ jest ustawiany w runtime

T binding = DataBindingUtil.inflate(…)

 

custom binding adapter

znajdowanie metody settera w kontrolce na podstawie nazwy atrybutu w bindingu (np. text –> setText, nie zawsze jest tak prosto, źródła Android 6.0 SDK, API 23)

własny adapter

@BindingAdapter(“android:src”)

public static void setImageUri(ImageView view, String imageUri) {

if (imageUri == null)

        view.setImageURI((Uri)null);

else

        view.setImageURI(Uri.parse(imageUri));

}

@BindingConversion

public static ColorDrawable convertColorToDrawable(int color) {

return new ColorDrawable(color);

}

bindowanie numeru do propercji string, metoda z atrybutem BindingAdapter w aktywności

Event binding

auto

android:onClickListener=”@{listeners}”

public void setOnClickListener(View.OnClickListener l)

public interface OnClickListener {

void onClick(View v);

}

renamed

android:onClick=”@{listeners}”

@BindingMethods({

@BindingMethod(

    type = View.class,

    attributte =”android:onClick”

    method=”setOnClickListener”),…})

public class ViewBindingAdapter { … }

custom

OnAttachStateChangeListener

android:onViewAttachedToWindow=”@{listeners}”

android:onViewDetachedFromWindow=”@{listeners}”

 

handlery w event bindingu

 

dynamiczny binding

type=”List&lt;Person>”  //List<Person>

typ elementu w adapterze dla item-u w RecycleView

@Override

public int getItemViewType(int position) { … }

@Override

public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { …}

onBindViewHolder:  binding.setVariable(BR.person, person);  //(id, object)

 

 

Observables

activity

onCreate:

binding.setListeners(this);

public void onCheckChanged(CompoundButton view, boolean isChecked) {

binding.setIsOn(isChecked);

}

<data>

<variable name=”isOn” type=”boolean” />

<variable name=”listeners” type=”com.xxx.yyy.MainActivity”/>

<Switch … 

      android:onCheckedChanged=”@{listeners.onCheckChanged}”/>

 

możliwość MVVM

po każdej zmianie trzeba na nowo ustawić w bindingu viewModel, obsługa handlerów poza view modelem

można lepiej

public static class ViewModel extends BaseObservable {

        @Bindable public final ObservableBoolean isOn = new ObservableBoolean();

         …

}

nie trzeba wtedy na nowo ustawiać view modelu po każdej jego zmianie

viewModel.isOn.set(isChecked);

//od 1.0-rc2 +

Typy obserwowalne:

android.database

ContentObservable

android.view

ViewTreeObserver

rx.Observable

 

interfejs Observable

public interface Observable {

void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);

void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);

abstract class OnPropertyChangedCallback {

      public abstract void onPropertyChanged(Observable sender, int propertyId);

}

}

 

klasa BaseObservable

public class BaseObservable implements Observable {

public synchronized void notifyChange() { … }

public void notifyPropertyChanged(int fieldId) { … }

}

 

prymitywne Observables

ObservableBoolean

ObservableByte

ObservableChar

ObservableDouble

ObservableFloat

ObservableInt

ObservableLong

ObservableShort

 

Observable<T>  //nie implementuje Parcelable

ObservableParcelable<T extends Parcelable> // extends ObservableField<T>

ObservableList<> // konkretna implementacja:  ObservableArrayList<>

callback ma kilka metod (onChanged,  onItemRangeChanged, onItemRangeInserted, onItemRangeMoved, onItemRangeRemoved)

ListChangeRegistry

ObservableMap<> // –//-: ObservableArrayMap<>

callback: jedna metoda onMapChanged

MapChangeRegistry

 

własne obiekty obserwowalne:  Observable, BaseObservable lub pola ObservableField

public class CustomObservable implements BaseObservable {

private boolean breaker;

@Bindable public boolean getBreaker() {return breaker; }

public void setBreaker(boolean breaker) {

        if (this.breaker != breaker) {

              this.breaker = breaker;

              notifyPropertyChanged(BR.breaker);

          }

}

}

 

adaptery bindingu w osobnej klasie lub w aktywności lub view modelu