RSS
poniedziałek, 12 września 2016
Programowanie iPhone / iOS . Jak zacząć?

Od jakiegoś czasu tutoriale o programowaniu iPhone / iOS na moim blogu przeżywają drugą młodość. Oczywiście nie da się ukryć, że niestety nie są one najnowsze. Mimo iż nie są to teksty całkiem oderwane od rzeczywistości, technologia poszła nieco do przodu i iOS troszkę się zmienił :) Ponieważ otrzymałem wiele próśb i zapytań o uaktualnienie podstawowego kursu programowania iPhone / iOS w ciągu kilku najbliższych paru tygodni postaram się materiały na stronie nieco odświeżyć. W związku z wypuszczeniem nowej wersji iOS - iOS 10 wydaje się to jeszcze bardziej na czasie.

 

Zaczniemy od tego, co potrzebne jest, aby zacząć zabawę z programowanie iPhone / iOS. W zasadzie wystarcza dwie rzeczy Mac i środowisko programowania Xcode.

 

1. 1. Przede wszystkim potrzebny jest Mac z zainstalowanym systemem operacyjnym min. OS X 10.11. Jeśli korzystasz ze starszego systemu, powinieneś mieć możliwość jego bezpłatnej aktualizacji, zanim jednak się na takową zdecydujesz sprawdź, czy twój sprzęt spełnia jego minimalne wymagania. Ja w tej chwili korzystam z MacBook-a Pro z 2010 roku i daje radę.
2. 2. Do pisania kodu potrzebować będziemy środowiska Xcode. Korzystać będziemy z najnowszej wersji (obecnie jest to 7.3.1), którą możecie pobrać bądź z AppStore, bądź też ze stron developerskich Apple, po zarejestrowaniu się, jako developer.

 

W zasadzie żeby zacząć zabawę rejestracja w programie developerskim Apple nie jest konieczna. Samo posiadanie Apple ID pozwala na bezpłatne pobranie narzędzi Xcode, bibliotek SDK i innych zasobów. (Jeśli nie masz jeszcze Apple ID możesz założyć go tutaj. Jest to idealne rozwiązanie, jeśli chcesz zacząć zabawę z programowanie iPhone / iOS. W przypadku jednak, gdy będziesz chciał zacząć dystrybuować swoje aplikacje poprzez AppStore, konieczna będzie rejestracja w programów developerskim Apple. Roczny koszt tej przyjemności to $99. Poniżej zestawienie tego, co oferuje dostęp poprzez logowanie z wykorzystaniem Apple ID w porównaniu z programem płatnym.

 

Tabela 1. Apple ID vs Program Programistów



Generalnie, jeśli dopiero zaczynasz, aby zminimalizować koszty, polecam skorzystanie z bezpłatnej opcji. Często tworzenie oprogramowania zajmuje nieco dłużej niż przewidujemy. Jeśli już będziesz gotowy do szerszego testowania i dystrybucji oprogramowania na AppStore, dopiero wtedy zapisz się do programu. Więcej informacji na temat korzyści płynących z przystąpienia do programu znajdziecie tutaj.

 

Poza wspomnianymi wyżej dwoma programami; bezpłatnym opartym na Apple i płatnym dla programistów indywidualnych, Apple oferuje dwa dodatkowe programy. Jest to program developerski dla firm oraz program „Enterprise” dla dużych organizacji chcących rozprowadzać swoje aplikację wewnątrz organizacji. Nie będę się tu wgłębiał w żadna z tych opcji, więcej informacji znajdziecie na stronach Apple.

poniedziałek, 29 sierpnia 2016
iOS / iPhone - jak przechowywać, zapisywać dane aplikacji

Po przerwie, temat rozgrzewający (dla mnie) a mianowicie zestawienia sposobów przechowywania danych aplikacji na urządzeniach iOS.

 

 

Wykorzystanie NSUserDefaults jest chyba najprostszym sposobem, w jaki możemy przechowywać dane naszej aplikacji. Klasa ta powinna (może) być wykorzystywana do zapisywania prostych informacji konfiguracyjnych naszego programu, na przykład flagi, czy jest to jego pierwsze uruchomienie / pierwsze uruchomienie po aktualizacji czy tez nie, bądź innych informacji konfiguracyjnych (preferencji użytkownika).

 

 

Keychain, jako bezpiecznym mechanizm przechowywania danych i jako taki powinien być wykorzystywany do zapisu danych wrażliwych, takich jak informacje prywatne użytkowników bądź nazwy i hasła.

 

Listy wlaściwości PLists możemy z powodzeniem wykorzystywać do przechowania większych ilości, ale bez przesady) danych posiadających określoną strukturę (ang. structured data). Przykładowo, PLists możemy użyć do zapisu wygenerowanych przez użytkownika listy pozycji geograficznych, bądź też użyć mechanizmu do przechowywania danych demo naszej aplikacji. Jeśli pracujemy nad biblioteką, którą będziemy udostępniać innym programistom, PLists mogą być użyte, jako platforma konfiguracyjna dla osób, pracujących z naszą biblioteka.

 

 

Archiwizacje obiektów (ang. object archiving) możemy wykorzystać do serializacji / deserializacji bądź danych binarnych, bądź danych strukturalnych w przypadku, gdy nie chcemy, bądź nie możemy do ich przechowywania użyć PLists.

 

 

Core Data to stosunkowo skomplikowany (niektórzy mogą się tu ze mną nie zgodzić) mechanizm przechowywania danych wprowadzonych przez Apple wraz z wersją 3.0 systemu iOS. Wykorzystywany może być przede wszystkim do przechowywania większej ilości danych powiązanych ze sobą zależnościami (relacjami). Core Data, jako backend wykorzystywać może nie tylko SQLite, o którym powiemy sobie krótko za chwile, ale także przykładowo pliki XML, bądź inne w opracowanym przez programistę formacie. Backend, jest obsługiwany automatycznie i nie wymaga bezpośrednich interwencji programisty. Do innych zalet należą przykładowo mechanizmy „undo”, „redo” i integracja z iCloud.

 

 

SQLite jest moim ulubionym mechanizmem przechowywania danych relacyjnych, najprawdopodobniej ze względu na szybkość, nieduże wymagania i moje wcześniejsze, bogate doświadczenia z SQL-em i relacyjnymi bazami danych. Powinien być wykorzystywany, gdy zależy nam na szybkości, gdy chcemy, aby nasze dane były dostępne na wielu platformach (przykładowo na iOS i Androidzie). Za wadę w tym wypadku można uznać podatność na błędy programistyczne i konieczność ręcznego oprogramowania niektórych mechanizmów jak na przykład wspomnianej integracji z iCloud. Ciekawe porównanie SQLite z CoreData znajdziecie w artykule Deana Gereaux zatutyłowanym iOS Data Storage: Core Data vs. SQLite.

 

 

Gdy żaden z powyższych mechanizmów nie spełnia naszych wymagań, przykładowo, gdy dane nie powinny znajdować się na urządzeniu użytkownika, gdy przykładowo chcemy, aby były dzielone z / udostępniane innym użytkownikom naszej aplikacji, powinniśmy zdecydować się na ich przechowywanie na serwerach zewnętrznych. Oczywiście, gdy jedyne, na czym nam zależy jest synchronizacja danych pomiędzy rożnymi urządzeniami, w pierwszym wypadku powinniśmy rozważyć iCloud.

 

 

Zestawienie oparte o własne obserwacje i listę udostępnioną przez Allesandro Orru. Jeśli coś istotnego pominąłem, proszę dać znać!

sobota, 17 października 2015
Poczta Polska – Śledzenie Przesyłek w .NET / C#

Całkiem przypadkiem, czekając na przesyłkę, zauważyłem na stronie Poczty Polskiej usługę webservice umożliwiającą sprawdzenie, co się dzieje z moją paczuszką. Jako, że mam zacięcie programistyczne postanowiłem szybko sprawdzić jak to działa.

 

Korzystanie z serwisu nie jest skomplikowane. Wszystko zaczęło działać po drugiej kompilacji (to dużo biorąc pod uwagę jak prosty był kod, który napisałem).

 

 

using System;
using PPSledzenie.PocztaPolskaSledzenieSrv;

namespace PPSledzenie
{
    class Program
    {
        static void Main(string[] args)
        {
            SledzeniePortTypeClient ppService = new SledzeniePortTypeClient();
            ppService.ClientCredentials.UserName.UserName = "sledzenie";
            ppService.ClientCredentials.UserName.Password = "PPSA";
            Przesylka paczkaSzczegoły = ppService.sprawdzPrzesylke("testk-1"); //testowy numer przesylki
        }
    }
}

 

Na początku nie mogłem się wcale pod serwis podłączyć („Add Service Reference…” – end point: https://tt.poczta-polska.pl/Sledzenie/services/Sledzenie?wsdl). Mój błąd.

Zapomniałem, że jestem za Proxy i konieczna jest nieznaczna modyfikacja pliku konfiguracyjnego:

 

 20151017_222906

 

Po tej drobnej zmianie nie było już problemu z dodaniem referencji. Niestety po pierwszej kompilacji, próba odczytu danych przesyłki zakończyła się wystąpienie wyjątku:

 

System.ServiceModel.FaultException was unhandled
  HResult=-2146233087
  Message=WSDoAllReceiver: Incoming message does not contain required Security header
  ...

 

Konieczna była wiec kolejna modyfikacja pliku konfiguracyjnego. Wprowadzone zmiany zaznaczyłem na czerowno.

 

20151017_223239

 

Mimo mojego „czujnego oka” przesyłka priorytetowa nie dotarła dzisiaj (piątek), a ponieważ w weekendy poczta lokalna nie pracuje musze czekać do poniedziałku.

 

A… jeśli przypadkiem natkniecie się na poniższy wyjątek:

 

System.ServiceModel.FaultException was unhandled
  HResult=-2146233087
  Message=WSDoAllReceiver: security processing failed
...

 

Zanim zaczniecie rwać sobie włosy z głowy, sprawdźcie czy nazwa użytkownika i hasło są poprawne :)

 

Na poniższym screenie widać przykładowe dane o przesyłce (debug).

 

Poczta Polska – Śledzenie Przesyłek w .NET / C#

 

Aby sprawdzić, co dokładnie oferuje web service, polecam zapoznanie się z dokumentacją (otwiera sie w nowym oknie).

 

www.poczta-polska.pl/pliki/webservices/Metody i struktury uslugi sieciowej Poczty Polskiej SA.pdf

środa, 14 października 2015
Oracle - bezpieczeństwo na poziomie wierszy (ang. row level security)

 

Jeśli masz dostęp do edycji Enterprise Oracle, masz również możliwość wykorzystania mechanizmu VPD – Virtual Private Database, który może być użyty do implementacji bezpieczeństwa na poziomie wierszy (ang. row level security).

 

Bezpieczeństwo oparte o VPD działa w oparciu o dodawanie, do każdego wykonywanego polecenia SQL, predykatu zdefiniowanego w odpowiedniej funkcji jak pokazuje to poniższy przykład:

 

select /*+ zapytanie rls */ * from ( 
    select /*+ nasze zapytanie */ ... from TABELA
    where WARTOSC = 42 )
where rls_policy.FUNKCJA_NA_TABELI = 'true'

 

Podstawowe elementy mechanizmu VPD to:

 

1. Kontekst (ang. Context) – specyficzny dla danej aplikacji, możliwy do ustawianie tylko i wyłącznie za pomocą specyficznej procedury.
2. Procedura Kontekstu (ang. Context Procedure) – procedura definiująca pary klucz – wartość dla danego kontekstu.
3. Polisa (ang. Policy) – określa, jakiego obiektu / tabeli będą dotyczyły restrykcje.
4. Funkcja Polisy (ang. Policy Function) – zwraca definicje predykaty, który powinien być dodany do każdego polecenia SQL.

 

A jak to wygląda w praktyce?

 

Kontekst

 

Pierwsza rzeczą, której potrzebujemy, jest obiekt kontekstu. Za pomocą klauzuli USING określamy zaufany obiekt, który będzie mógł modyfikować pary klucz – wartość kontekstu.

 

CREATE CONTEXT USER_CTX1 USING SECURITY_PACKAGE1;
/

 

Procedura Kontekstu

 

Następnym elementem jest procedura kontekstu odpowiadająca za ustawianie odpowiednich wartości utworzonego właśnie obiektu kontekstu.

 

CREATE OR REPLACE PACKAGE SECURITY_PACKAGE1
IS

   PROCEDURE SET_USER_CTX (ps_key IN VARCHAR2, ps_val IN VARCHAR2);

END SECURITY_PACKAGE1;
/

CREATE OR REPLACE PACKAGE BODY SECURITY_PACKAGE1
IS

   GS_USER_CTX_VNAME VARCHAR2(30) := 'USER_CTX1';
   
   PROCEDURE SET_USER_CTX (ps_key IN VARCHAR2, ps_val IN VARCHAR2)
   IS
   BEGIN
      DBMS_SESSION.SET_CONTEXT (GS_USER_CTX_VNAME, ps_key, ps_val);
   END SET_USER_CTX;

END SECURITY_PACKAGE1;
/

 

Polisa

 

W trzecim kroku utworzymy polisę, w której określamy, jakiego obiektu/tabeli nasze restrykcje dotyczą:

 

BEGIN
   DBMS_RLS.ADD_POLICY (
      object_schema     => 'HR',
      object_name       => 'EMPLOYEES',
      policy_name       => 'HR_DEPARTMENT_ID_POLICY',
      function_schema   => 'HR',
      policy_function   => 'SECURITY_PACKAGE1.DEPARTMENT_ID_RESTRICTION',
      statement_types   => 'INSERT, UPDATE, DELETE, SELECT',
      update_check      => TRUE,
      policy_type       => DBMS_RLS.SHARED_CONTEXT_SENSITIVE);
END;
/

 

Nie będę tu wnikał w szczegóły przekazywanych parametrów, zainteresowanych odsyłam do źródła:

 

Oracle 11g:

http://docs.oracle.com/cd/E11882_01/appdev.112/e40758/d_rls.htm#ARPLS052

Oracle 12c:

https://docs.oracle.com/database/121/ARPLS/d_rls.htm#ARPLS052

 

 

Funkcja Polisy

 

W ostatnim kroku implementacji musimy utworzyć funkcje polisy (predykatu). Dodany kod w pakiecie zaznaczyłem na czerwono.

 

CREATE OR REPLACE PACKAGE SECURITY_PACKAGE1
IS

   PROCEDURE SET_USER_CTX (ps_attr IN VARCHAR2, ps_val IN VARCHAR2);

   FUNCTION DEPARTMENT_ID_RESTRICTION (ps_schema_name   IN VARCHAR2,
                                       ps_object_name   IN VARCHAR2) RETURN VARCHAR2;

END SECURITY_PACKAGE1;
/

CREATE OR REPLACE PACKAGE BODY SECURITY_PACKAGE1
IS

   GS_USER_CTX_VNAME VARCHAR2(30) := 'USER_CTX1';
   
   PROCEDURE SET_USER_CTX (ps_attr IN VARCHAR2, ps_val IN VARCHAR2)
   IS
   BEGIN
      DBMS_SESSION.SET_CONTEXT (GS_USER_CTX_VNAME, ps_attr, ps_val);
   END SET_USER_CTX;

   FUNCTION DEPARTMENT_ID_RESTRICTION (ps_schema_name   IN VARCHAR2,
                                       ps_object_name   IN VARCHAR2) RETURN VARCHAR2
   IS
      ls_return_val   VARCHAR2 (200);
   BEGIN
      
      IF ps_schema_name = USER                --wlasciciel widzi wszystko
      THEN
         ls_return_val := NULL;
      ELSIF SYS_CONTEXT (GS_USER_CTX_VNAME, 'DEPARTMENT_ID') IS NOT NULL
      THEN
         ls_return_val := 'DEPARTMENT_ID IN (' || SYS_CONTEXT (GS_USER_CTX_VNAME, 'DEPARTMENT_ID') || ')';
      ELSE    -- just for testing to simulate the user not having anything set
         ls_return_val := '1=1';
      END IF;

      RETURN ls_return_val;
      
   END DEPARTMENT_ID_RESTRICTION;
   
END SECURITY_PACKAGE1;
/

 

Pamiętaj! Funkcje predykatów powinny być szybkie i wydaje, ponieważ ewaluowane są dla każdego wykonywanego polecenia SQL.

 

Testowanie

 

Przykłady poniższe wykorzystują standardowe tabele schematu HR.

 

SELECT COUNT (*), DEPARTMENT_ID
    FROM EMPLOYEES
GROUP BY DEPARTMENT_ID;

 

Po wykonaniu powyższego zapytania w moim wypadku otrzymałem 12 wiersze. Jeśli teraz zmienimy nieco kontekst użytkownika i wykonamy zapytanie ponownie, powinniśmy zobaczyć 4 wiersze. Wartości identyfikatorów departamentów (DEPARTMENT_ID) powinny odpowiadać wartościom zdefiniowanym w kontekście.

 

EXEC SECURITY_PACKAGE1.SET_USER_CTX('DEPARTMENT_ID', '70,110,50,80');

 

Jeśli cos się nie zgadza, widzisz na przykład więcej wierszy niż powinieneś, upewnij się ze nie wykonujesz zapytań, jako właściciel obiektu/tabeli. Mój kod zakłada ze właściciel może widzieć wszystko i predykat w tej sytuacji nie jest dodawany.

09:12, m0rt1m3r
Link Dodaj komentarz »
poniedziałek, 11 maja 2015
Przyszłość PowerBuilder-a

 

Dirk Boessmann, Senior Vice President SAP Mobile Development na spotkaniu użytkowników PowerBuilder-a w Karoliny Północnej, które miało miejsce 8go Maja 2015 przestawił prezentacje zatytułowana "Moving Forward with SAP PowerBuilder" – (wolne tłumaczenie: Przyszłość SAP PowerBuilder-a).

 

Boessmann oświadczył, iż 7-go Maja 2015 SAP podpisało z APPEON-em memorandum dające temu drugiemu dostęp do kodu źródłowego PowerBuilder-a oraz umożliwiające mu sprzedaż produktu. SAP oczywiście pozostanie w produkt zaangażowany w mniejszym lub większym stopniu.

 

Przyszłość PowerBuilder-a

 

 

Poniżej kilka najciekawszych wg mnie punktów z sesji Q&A

 

  • SAP zachowa prawa do własności intelektualnej, APPEON będzie zajmował się rozwojem i dystrybucja, najprawdopodobniej APPEON odpowiedzialny będzie również za wsparcie produktu. SAP nie będzie w żaden sposób wspierał finansowo PowerBuilder-a.

 

  • Doprecyzowanie kontraktu pomiędzy SAP i APPEON-em zajmie jeszcze kilka miesięcy, ale inżynierowie już rozpoczęli transfer informacji.

 

  • PowerBuilder nie pojawi się w ofercie SAP.

 

  • SAP nie będzie zajmował się promowaniem PowerBuilder-a.

 

  • SAP będzie się starał przedłużyć wsparcie dla serwera EA do 2017 roku a jednocześnie zaoferować drogę migracji dla innego rozwiązania.

 

Jeśli chcesz być bardziej bezpośrednio zaangażowany daj znać APPEON-owi, czego oczekujesz od kolejnych wersji PowerBuilder-a.

 

 

Dokładniejsza relacja z prezentacji i dodatkowe komentarze znajdziesz na stronach SCN.

 

 

10:11, m0rt1m3r
Link Komentarze (1) »
czwartek, 29 maja 2014
Implementacja C# Split() w PL/SQLu

 

Może się zdarzyć, że w tablicy asocjacyjnej musimy przechowywać złożone klucze główne tabel jako ciągi znaków oddzielone przecinkami (comma separated values). Aby łatwo było wyłuskać wartość konkretnego klucza głównego stworzyłęm prostą funkcje f_KeyOnPosition podobną w działania do funkcji Split c#:

 

String pkValue = csvKeys.Split[position];

 

I przykładowe wywołanie mojej implementacji w PL/SQLu:

vs_keyvalue VARCHAR2(100);
vs_keyvalue := f_Split('Ala,ma,3,koty', 3);

 

Zwracany typ VARCHAR2 jest najbardziej uniwersalny. Oczywiście jeśli typ klucza głównego jest inny wartość zwróconą będziemy musieli odpowiednio przekonwertować. Funkcja zwraca null jeśli pozycja klucza jest negatywna lub większa od liczby wartości przechowywanych w ciągu wejściowym, bądź gdy ciąg wejściowy jest null. Funkcja poniżej, być może komuś się przyda:

 

DECLARE
  vs_keystring VARCHAR2(200) := 'Ala,ma,3,koty';
  vs_keyvalue  VARCHAR2(100);
    
  --Careful may not be used in SQL!
  FUNCTION f_Split(vs_keyString VARCHAR2, vn_keyPosition NUMBER) RETURN VARCHAR2
  IS
    keyValue  VARCHAR2(100) := NULL;
  BEGIN
    IF vn_keyPosition > 0 THEN
  
      SELECT trim(regexp_substr (vs_keystring, '[^,]+', 1, vn_keyPosition)) 
        INTO keyValue 
        FROM dual
       WHERE ROWNUM = 1 
      CONNECT BY LEVEL <= LENGTH(regexp_replace (vs_keystring, '[^,]+'))  + 1;  

    END IF;
    
    RETURN keyValue;
  
  END;
  
BEGIN

  vs_keyvalue := f_Split(vs_keystring, 3);
  DBMS_OUTPUT.PUT_line(nvl(vs_keyvalue, 'null'));
  vs_keyvalue := f_Split(vs_keystring, -1);
  DBMS_OUTPUT.PUT_line(nvl(vs_keyvalue, 'null'));
  vs_keyvalue := f_Split(vs_keystring, 4);
  DBMS_OUTPUT.PUT_line(nvl(vs_keyvalue, 'null'));
  vs_keyvalue := f_Split(vs_keystring, 2);
  DBMS_OUTPUT.PUT_line(nvl(vs_keyvalue, 'null'));
  vs_keyvalue := f_Split(vs_keystring, 5);
  DBMS_OUTPUT.PUT_line(nvl(vs_keyvalue, 'null'));
  vs_keyvalue := f_Split(vs_keystring, 1);
  DBMS_OUTPUT.PUT_line(nvl(vs_keyvalue, 'null'));
  
END;
/

 

Rezultat działania:

3
null
koty
ma
null
Ala
środa, 23 kwietnia 2014
Programowanie Gier - główna pętla gry (ang. game loop)

Poniższy wpis jest tłumaczeniem popularnego postu Koena Wittersa opisującego różne podejścia do pętli gry i ich wady i zalety. Jest to również moje pierwsze podejście do tłumaczenia tekstu jeśli wiec macie jakieś uwagi dajcie znać.

Zapraszam do lektury.


Główna pętla (ang. game loop) stanowi serce każdej gry i żadna, ale to żadna, nie może się bez niej obyć. Niestety w Internecie brakuje dobrych materiałów na ten temat, co może stanowić niemały problem szczególnie, jeśli dopiero zaczynasz przygodę z programowaniem gier. Masz jednak szczęście :), trafiłeś właśnie na post, który poświęca temu zagadnieniu należną mu uwagę. Pracując jako programista gier, widziałem sporo gier mobilnych i zawsze zaskakiwała mnie różnorodność podejścia do tematu. Dziwić może, że tak prosty problem rozwiązywany jest na tak wiele różnych sposobów. Poniżej zaprezentuję najpopularniejsze implementacje i omówię ich wady i zalety, powiem też, która z nich, jest według mnie najlepsza.

 

Pętla gry

 

Każda gra składa się z szeregu wykonywanych po sobie czynności: odczytywania działań użytkownika, aktualizacji stanu gry, obsługi sztucznej inteligencji – AI, odtwarzania muzyki i efektów dźwiękowych oraz ostatecznie wyświetlenia (renderowania) gry. Ta sekwencja czynności obsługiwana jest właśnie przez pętlę gry, która to, jak wspomniałem na wstępie jest sercem każdej gry. W tym wpisie nie będziemy zajmować się jednak żadnym z wymienionych właśnie zagadnień, skupimy się natomiast na samej pętli gry. Aby uprościć nieco jej analizę, ograniczymy jej rolę do dwóch zadań: aktualizacji stanu gry i jej wyświetlania. Poniższy kod przedstawia pętlę gry w jej najprostszej postaci:

 

bool game_is_running = true;

while( game_is_running ) {
	update_game();
	display_game();
}

 

Problemem tej pętli jest to, że nie bierze ona pod uwagę upływającego czasu. Oznacza to, że na wolniejszych komputerach będzie ona działała wolniej, na szybszych z kolei, szybciej. Dawniej, gdy prędkość sprzętu, na którym uruchamiany będzie nasz program łatwiej było przewidzieć, nie stanowiło to większego problemu, dziś ze względu na różnorodność dostępnych platform i konfiguracji, musimy w jakiś sposób dodać do naszej pętli odpowiednią kontrolę. Możemy to zrobić na kilka sposobów, zanim jednak przejdziemy do szczegółów wprowadźmy dwa pojęcia:

 

Liczba klatek na sekundę - FPS (ang. frames per second)

 

Opierając się na powyższej implementacji pętli, wartość FPS reprezentuje ile razy w ciągu sekundy wykonana zostanie metoda display_game().

 

Prędkość Gry (ang. game speed)

 

Określa ile razy w ciągu sekundy uaktualniony zostanie stan gry - wykonana zostanie metoda update_game()

 

 

Liczba wyświetlanych klatek na sekundę (FPS) bazująca na stałej prędkości gry

 

Implementacja

 

const int FRAMES_PER_SECOND = 25;
const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND;

DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started

int sleep_time = 0;

bool game_is_running = true;

while( game_is_running ) {
	update_game();
	display_game();

	next_game_tick += SKIP_TICKS;
	sleep_time = next_game_tick - GetTickCount();
	if( sleep_time >= 0 ) {
		Sleep( sleep_time );
	}
	else {
		// Shit, we are running behind!
	}
}

 

Niewątpliwą zaletą tego rozwiązania jest jego prostota. Wiemy, że metoda update_game() będzie wywoływana dokładnie 25 razy na sekundę. Oznacza to, iż pisanie kodu gry powinno być dość proste. Przykładowo, implementacja powtórki (ang. replay) przy takim podejściu do pętli jest wręcz trywialna. Przy założeniu, że nie wykorzystujemy żadnych wartości losowych podczas gry wystarczy, że będziemy zapamiętywać - logować - zachowania gracza, tak abyśmy je mogli później odtworzyć. Na maszynie, na której pracujesz łatwo będzie dobrać idealną wartość stałej FRAMES_PER_SECOND, ale co stanie się, gdy naszą grę uruchomimy na szybszym lub wolniejszym komputerze?

 

Sprzęt wolniejszy

 

Jeśli komputer, na którym uruchamiamy grę będzie na tyle mocny, aby wyświetlić zdefiniowaną przez nas liczbę klatek na sekundę - FPS, nie będziemy mieli żadnego problemu. Pojawią się one jednak w momencie, gdy sprzęt nasz nie będzie w stanie sprostać zdefiniowanej wartości FPS. Spowoduje to, że gra zacznie działać wolniej. W najgorszym wypadku może dojść do sytuacji, w której w pewnym momentach, nasza gra będzie działała szybciej / normalnie, a w innych znacznie, znacznie wolniej, co sprawi, że pętla nie będzie mogła wykonać wszystkich niezbędnych operacji na czas i gra stanie się kompletnie niegrywalna.

 

Sprzęt szybszy

 

W tym wypadku gra nie będzie miała żadnych problemów z działaniem, podczas wykonywania pętli będziemy jednak tracić wiele cennych cykli zegara (wykorzystywanie jedynie 25-30 FPS w sytuacji, gdy moglibyśmy osiągnąć 300 FPS... to wstyd!). Nasza gra straci również nieco na atrakcyjności, co może być szczególnie widoczne podczas wyświetlania szybko poruszających się obiektów. Z drugiej jednak strony szczególnie, gdy celujemy w urządzenia mobilne, wspomniana wada może okazać się zaletą. Ograniczenie gry tak, aby nie wykorzystywała stale maksymalnej mocy obliczeniowej naszego urządzenia, ograniczy zużycie baterii.

 

Wnioski

 

Wykorzystanie tego podejścia (liczba wyświetlanych klatek na sekundę (FPS) bazująca na stałej prędkości gry) jest nieskomplikowanym rozwiązaniem, które możemy zaimplementować dość szybko, z którym jednak wiąże się kilka wad. Wysoka, pożądana wartość FPS spowoduje, że nasza gra może mieć problemy na wolniejszym, starszym sprzęcie. Niska wartość natomiast sprawi, że może ona sporo stracić wizualnie, gdy uruchamiana będzie na nowszych, szybszych komputerach.

 

 

Prędkość gry bazująca na zmiennej wartości wyświetlanych klatek - FPS

 

Implementacja

 

Kolejnym podejściem do implementacji pętli gry jest oprogramowanie jej w taki sposób, aby prędkość gry była tak wysoka jak to możliwe, w oparciu o wartość FPS. W tym wypadku, aktualizując grę pod uwagę bierzemy różnice czasu pomiędzy aktualną i poprzednio wyświetlaną ramką (klatką).

 

DWORD prev_frame_tick;
DWORD curr_frame_tick = GetTickCount();

bool game_is_running = true;
while( game_is_running ) {
	prev_frame_tick = curr_frame_tick;
	curr_frame_tick = GetTickCount();
	update_game( curr_frame_tick - prev_frame_tick );
	display_game();
}

 

Oczywiście kod gry w tym wypadku będzie nieco bardziej skomplikowany, ponieważ implementując metodę update_game() będziemy musieli brać pod uwagę różnicę czasu pomiędzy kolejnymi ramkami. Implementacja ta jest nieco trudniejsza, ale oczywiście możliwa do wykonania. Na pierwszy rzut oka pętla taka wygląda też na idealne podejście do problemu optymalnej pętli gry i w swojej karierze widziałem wielu utalentowanych programistów, którzy zdecydowali się na takie właśnie podejście. Kilku z nich najprawdopodobniej później żałowało, że nie widziało tego wpisu przed napisaniem swojego kodu :) za chwilę pokaże, że rozwiązanie to może mieć bardzo duże, negatywne implikacje zarówno na wolnym jak i na szybkim (tak to nie pomyłka - na szybkim) sprzęcie.

 

Sprzęt wolniejszy

 

Na sprzęcie wolniejszym, z różnych przyczyn od czasu do czasu mogą wystąpić spowolnienia, co sprawi, że nasza gra stanie się nieco ociężała. Może to wystąpić np. w grach wykorzystujących grafikę 3D, gdy próbujemy wyświetlać zbyt wiele wielokątów jednocześnie. Spadająca liczba wyświetlanych klatek (ang. frame rate) wpłynie na to, jak szybko nasza gra będzie reagować na to, co robi użytkownik (ang. user input), z drugiej strony zmieni to także czas i sposób reakcji gracza na wyświetlaną na ekranie akcje. Opóźnienie odbije się również na aktualizacji stanu gry, będzie on zmieniany w większych krokach / odstępach (ang. time chunks), co jeszcze bardziej obniży czas reakcji naszego gracza, jak i czas reakcji AI. W efekcie sprawi to, że nawet proste manewry mogą stać się bardzo trudne do wykonania, bądź kompletnie się nie udać. Przykładowo przeszkoda, którą ominęlibyśmy z łatwości przy wysokim FPS, może być niemożliwa do pokonania, gdy FPS spadnie do zbyt niskiej wartości. Znacznie poważniejszym problemem z wolnym sprzętem jest to, że w wypadku, gdy wykorzystujemy np. fizykę, nasza symulacja może dosłownie „eksplodować ”!

 

Sprzęt szybszy

 

Z pewnością zastanawiasz się, jakie problemy z powyższa pętlą mogą pojawić się na szybszym sprzęcie. Zanim to wyjaśnimy, przypomnijmy sobie kilka informacji na temat tego, jak operacje matematyczne wykonywane są na komputerach. Pamięć przeznaczona na zmienne typu float i double jest ograniczona, co wiąże się z tym, iż pewne wartości nie mogą być poprawnie reprezentowane. Przykładowo wartość 0.1 nie może być przedstawiona poprawnie binarnie, gdy wykorzystujemy typ double, w związku z czym jest ona zaokrąglana. Spójrz na poniższy przykładowy kod w Pythonie:

 

>>> 0.1
0.10000000000000001

 

Na razie nie wygląda to na nic szczególnie poważnego, ale konsekwencje tej „drobnostki” już mogą takie być. Załóżmy, że pracujemy nad grą wyścigami samochodowymi (ang. car race-game). Załóżmy dalej, że prędkość samochodu to 0.001 jednostek na milisekundę (ang. units/ms). Po 10 sekundach nasze auto przemieści się więc o 10.0 jednostek. Jeśli teraz rozbijemy nasze obliczenia przebytego dystansu tak, jak zrobiłaby to nasza pętla gry otrzymamy poniższą funkcję przyjmującą, jako parametr wartość FPS:

 

>>> def get_distance( fps ):
... 	skip_ticks = 1000 / fps
... 	total_ticks = 0
... 	distance = 0.0
... 	speed_per_tick = 0.001
... 	while total_ticks < 10000:
... 		distance += speed_per_tick * skip_ticks
... 		total_ticks += skip_ticks
... return distance

 

Teraz obliczmy dystans samochodu dla wartości FPS = 40:

 

>>> get_distance( 40 )
10.000000000000075

 

Wow... to nie 10.0, tak jak się spodziewaliśmy! Co się więc stało? Ponieważ podzieliliśmy nasze obliczenia na 400 części, błąd zaokrąglenia znacznie wzrósł. Sprawdźmy teraz rezultat działania naszej funkcji przy wartości FPS = 100:

 

>>> get_distance( 100 )
9.9999999999998312

 

Tym razem błąd jest jeszcze większy. Odpowiadają za to operacje dodawania wykonywane, w tym wypadku, jeszcze częściej. Sprawdźmy ile wynosi różnica dystansu wyliczona w obu przypadkach dla wartości FPS = 40 oraz FPS = 100:

 

>>> get_distance( 40 ) - get_distance( 100 )
2.4336088699783431e-13

 

Mogłoby się wydawać, że wartość jest na tyle mała, iż można ją zignorować, ponieważ nie będzie tak naprawdę zauważalna w grze. Prawdziwy problem zacznie się jednak, gdy ta złą wartość zaczniemy wykorzystywać, jako bazę dla dalszych obliczeń. Może to spowodować, że początkowo mały błąd urośnie do ogromnych rozmiarów, powodując, że nasza gra stanie się niegrywalna przy wysokich wartościach FPS. Jak duże jest prawdopodobieństwo, że z podobną sytuacją spotkamy się w praktyce? Wydaje mi się, że w zupełności wystarczające, aby brać je pod uwagę. Widziałem w przeszłości grę implementującą tego typu pętlę i faktycznie miała ona poważne problemy, gdy wyświetlana liczba klatek na sekundę była wysoka. Programiście w końcu udało się zlokalizować błąd, ale jego poprawienie wiązało się z przepisaniem sporej ilości istniejącego kodu.

 

Wnioski

 

Podejście to, wyglądające na pierwszy rzut oka na bardzo dobre, może spowodować problemy zarówno, gdy nasza gra uruchamiana jest na wolnym jak i na szybkim sprzęcie. Dodatkowo implementacja kodu aktualizującego stan gry jest w tym wypadku trudniejsza, w porównaniu z implementacją wykorzystującą pętlę bazującą na stałej wartości FPS. Nie warto więc chyba tego podejścia wykorzystywać.

 

 

Stała prędkość gry z ograniczoną maksymalna wartością FPS

 

Pierwsze z prezentowanych rozwiązań (liczba wyświetlanych klatek na sekundę (FPS) bazująca na stałej prędkości gry) ma problemy z działaniem na wolniejszych maszynach. W tym wypadku zarówno prędkość gry jak i częstotliwość wyświetlania klatek na ekranie (ang. framerate) spadnie. Jednym z możliwych rozwiązań jest aktualizacja stanu gry z częstotliwością odpowiadającą częstotliwości odświeżania, przy jednoczesnym zmniejszeniu częstotliwości renderowania obrazu. Możemy to osiągnąć modyfikując nasza pętlę gry tak, jak to pokazałem poniżej.

 

Implementacja

 

const int TICKS_PER_SECOND = 50;
const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
const int MAX_FRAMESKIP = 10;

DWORD next_game_tick = GetTickCount();
int loops;

bool game_is_running = true;
while( game_is_running ) {
	loops = 0;
	while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
		update_game();
		next_game_tick += SKIP_TICKS;
		loops++;
	}
	display_game();
}

 

W tym wypadku stan naszej gry aktualizowany będzie 50 razy na sekundę i renderowany tak szybko, jak to tylko możliwe. Warto zauważyć, że gdy rysowanie wykonywane jest częściej niż 50 razy na sekundę, kolejne rysowane klatki będą się powtarzać, wiec tak naprawdę ramki różniące się od siebie, będą wyświetlane na stałym, maksymalnym poziomie 50 ramek / klatek na sekundę. W momencie, gdy nasza gra uruchamiana będzie na wolniejszych komputerach, częstotliwość renderowania obrazu może spaść do minimalnego dozwolonego poziomu MAX_FRAMESKIP. W praktyce oznacza to, że w momencie, gdy wartość FPS spadnie poniżej 5 (FRAMES_PER_SECOND / MAX_FRAMESKIP), gra zwolni.

 

Sprzęt wolniejszy

 

Na wolnych komputerach liczba wyświetlanych klatek na sekundę spadnie, ale sama gra powinna wciąż działać poprawnie / z normalna prędkością. Jeśli maszyna okaże się faktycznie zbyt słaba, gra zacznie działać wolniej i wyświetlany obraz nie będzie płynny.

 

Sprzęt szybszy

 

W tym wypadku gra nie powinna mieć żadnych problemów, ale podobnie jak w opcji pierwszej tak i tu tracić będziemy cenne cykle zegara, które mógłby by być wykorzystane przykładowo, do generowania płynniejszego obrazu (wyższa wartość frame rate). Odnalezienie właściwego balansu pomiędzy częstotliwością aktualizacji gry i możliwością jej poprawnego działania na wolniejszych komputerach jest w tym wypadku kluczowa.

 

Wnioski

 

Uzyskanie stałej prędkości gry przy jednoczesnym ograniczeniu maksymalnej wartości FPS jest rozwiązaniem nieskomplikowanym w implementacji i sprawia, że kod naszej gry jest stosunkowo prosty. Oczywiście podejście takie ma ciągle swoje wady. Ustawienie zbyt wysokiej wartości maksymalnej FPS może doprowadzić do problemów na słabszym sprzęcie, nie tak poważnych jednak jak te, które omówiliśmy w pierwszym przykładzie. Wykorzystanie z kolei wartości zbyt niskiej sprawi, że gra nasza nie będzie tak atrakcyjna, jak mogłaby być gdyby wykorzystywała w pełni możliwości szybkiego komputera.

 

 

Stała prędkość gry niezależna od zmiennej wartości FPS

 

Implementacja

 

Czy istnieje zatem, możliwość dalszego usprawnienia powyższego rozwiązania, tak aby nasza gra działała szybciej na wolnym sprzęcie, a jednocześnie lepiej wykorzystywała możliwości szybszych komputerów i wyglądała na nich atrakcyjniej? Na szczęście dla nas jest to możliwe. Stan gry nie musi być przecież aktualizowany 60 razy na sekundę. W większości zastosowań wystarczające powinno być odczytywanie akcji użytkownika, zamiany AI i aktualizacja stanu gry 25 razy na sekundę. Spróbujmy więc zmodyfikować naszą pętlę tak, aby wywoływała ona metodę update_game() dokładnie 25 razy na sekundę. Z drugiej strony, odświeżanie ekranu powinno odbywać się tak często, jak jest to tylko możliwe na sprzęcie, którym dysponujemy. Oczywiście niska prędkość odświeżania (slow frame rate) nie powinna odbić się negatywnie na aktualizacji stanu gry. Poniższy kod pokazuje, jak możemy to wszystko osiągnąć.

 

const int TICKS_PER_SECOND = 25;
const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
const int MAX_FRAMESKIP = 5;

DWORD next_game_tick = GetTickCount();
int loops;
float interpolation;

bool game_is_running = true;
while( game_is_running ) {
	loops = 0;
	while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
		update_game();
		next_game_tick += SKIP_TICKS;
		loops++;
	}
	interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
					/ float( SKIP_TICKS );
	display_game( interpolation );
}

 

Wykorzystując tą implementację, metoda update_game() pozostaje prosta. Niestety komplikuje się za to metoda display_game(). Konieczna jest bowiem implementacja funkcji przewidującej (ang. prediction function) wykorzystującej jako argument interpolację. Za chwile wyjaśnimy sobie jak oba mechanizmy działają, ale najpierw pokażmy, dlaczego są one potrzebne.

 

Dlaczego interpolacja?

 

Stan gry aktualizowany jest 25 razy na sekundę, tak wiec, jeśli nie skorzystamy z interpolacji, przy renderowaniu poszczególne klatki wyświetlane będą z ta sama prędkością. Warto przy tym pamiętać, że 25 klatek na sekundę to nic złego, filmy przykładowo, wyświetlane są z prędkością 24 klatek na sekundę. Dlatego też 25 FPS powinno wystarczyć, aby nasza gra była wizualnie atrakcyjna, oczywiście szybko poruszające się obiekty zyskają na atrakcyjności przy wyższych wartościach FPS. Co możemy zrobić, to sprawić, że ich ruch będzie płynniejszy pomiędzy poszczególnymi klatkami. To właśnie do tego przydadzą się nam: funkcja przewidująca i interpolacja.

 

Interpolacja i przewidywanie

 

Tak jak już wspomniałem, aktualizacja stanu gry odbywa się ze stałą prędkością (określoną ilość razy na sekundę) dlatego też, kiedy rysujemy/renderujemy scenę możliwe jest, iż znajdzie się ona pomiędzy dwoma kolejnymi aktualizacjami stanu (org. ang. gametick). Wyobraźmy sobie dalej, że właśnie po raz 10-ty zaktualizowaliśmy stan gry i zaczynamy rysować/renderować naszą scenę. Rysowanie odbędzie się więc pomiędzy 10-ą a 11-ą aktualizacją. Możliwie więc jest, że rysowanie powinno się odbyć na pozycji 10.3. Wartość interpolacji w tym wypadku to właśnie 0.3. Rozważmy następującą sytuację: posiadamy samochód, który porusza się wraz z każdą aktualizacją stanu następująco:

 

position = position + speed;

 

Jeśli podczas 10-go taktu pozycja auta to 500, a prędkość równa jest 100, wtedy przy 11 aktualizacji pozycja powinna wynieść 600. Powstaje pytanie, gdzie umieścić samochód podczas wyświetlania? Możemy w tym celu wykorzystać pozycję wyliczoną podczas ostatniej aktualizacji stanu gry (w tym wypadku jest to 500). Lepszym podejściem jest jednak określenie w przybliżeniu, gdzie auto powinno się znaleźć dla wartości 10.3. Odpowiednie obliczenie przedstawiam poniżej:

 

view_position = position + (speed * interpolation)

 

W naszym przykładzie samochód rysowany byłby na pozycji 530. Zmienna ‘interpolation’ reprezentuje wartość pomiędzy poprzednią, a następną aktualizacją stanu gry (poprzednia = 0.0, następna = 1.0). Teraz musimy opracować funkcję przewidującą, obliczającą gdzie kamera/auto powinno się znaleźć w momencie, gdy scena jest rysowana. Funkcja ta może być oparta na prędkości obiektu, sterowaniu, bądź prędkości obrotowej. Nie musi być skomplikowana, ponieważ wykorzystujemy ją jedynie do „wygładzenia” ruchu pomiędzy poszczególnymi klatkami. Przy takim podejściu jest oczywiście możliwe, że nasz obiekt zostanie narysowany na innym na moment przed wykryciem kolizji. Ponieważ jednak stan gry aktualizowany jest 25 razy na sekundę, jeśli się stanie coś podobnego, to błąd będzie widoczny przez ułamek sekundy i powinien być tak naprawdę niezauważalny dla ludzkiego oka.

 

Sprzęt wolniejszy

 

W większości wypadków wykonanie metody update_game() zajmie znacznie mniej czasu niż wykonanie metody display_game(). W praktyce, możemy z dużym prawdopodobieństwem założyć, że nawet na wolnych komputerach funkcja update_game() wykonywana będzie 25 razy na sekundę, co pozwoli na bezproblemowa interpretację akcji użytkownika i aktualizację stanu gry nawet wtedy, gdy scena gry odświeżana będzie z prędkością przykładowo 15 klatek na sekundę.

 

Sprzęt szybszy

 

Na szybkich maszynach prędkość naszej gry będzie stała i wyniesie 25 aktualizacji na sekundę, oczywiście samo odświeżanie ekranu będzie odbywało się znacznie częściej. Interpolacja sprawi, że gracz będzie miał wrażenie, iż wszystko działa płynniej, ze znacznie większą prędkością, dzięki czemu gra stanie się atrakcyjniejsza wizualnie. Jest to swojego rodzaju oszukiwanie, jeśli chodzi o liczbę wyświetlanych klatek gry – FPS, ponieważ tak naprawdę nie aktualizujemy stanu gry co klatkę, a jedynie odświeżamy jego przybliżoną, wizualną reprezentację. Dzięki temu sceny naszej gry rysowane/renderowane są z wyższą częstotliwością, niż w drugiej z opisanych wcześniej metod.

 

Wnioski

 

Pętla gry, w której aktualizacja stanu gry jest niezależna od liczby wyświetlanych na sekundę klatek, wydaje się być najlepszą z przedstawionych implementacji. W tym wypadku jednak musimy pamiętać o opracowaniu funkcji przewidującej w metodzie display_game(), co nie jest trudne do osiągniecia.

 

Wnioski końcowe

 

Pętla gry to znacznie więcej niż mogłoby się na pierwszy rzut oka wydawać. Przedstawiliśmy tu cztery możliwe implementacje, z których jednej powinieneś zdecydowanie unikać. Jest to ta, gdzie zmienna wartość FPS odpowiada za prędkość gry. Stała prędkość odświeżania (frame rate) może być przydatnym i prostym rozwiązaniem w przypadku gier przeznaczonych na urządzenia mobilne. Jeśli jednak chcesz, aby możliwości sprzętu, na którym uruchomiona jest twoja gra wykorzystane były optymalnie, najlepszym podejściem jest pętla gry, w której wartości odświeżania ekranu (liczba wyświetlanych na sekundę klatek - FPS) jest niezależna od prędkości gry (aktualizacji jej stanu) i wykorzystuje funkcję przewidującą i interpolację. Jeśli nie chcesz spędzać czasu programując funkcję przewidującą (prediction function) możesz z powodzeniem wykorzystać rozwiązanie oparte na maksymalnej wartości odświeżania (maximum frame rate). W tym wypadku jednak dobranie odpowiedniego maksimum dla maszyn wolnych i szybkich może okazać się niełatwe.

 

I to już wszystko. Teraz możesz już zabrać się za pisanie fantastycznej gry o stworzeniu, której zawsze marzyłeś!

 

Koen Witters

Translated by Damian/MORT

poniedziałek, 03 lutego 2014
Programowanie iPhone - Konwersja stringu (ciągu znaków) na datę – NSDate

 

Konwersja ciągu znaków na datę - NSDate jest jedną z bardzo często wykonywanych operacji, która jednocześnie sprawia bardzo wiele kłopotów początkującym.

 

Aby przekształcić ciąg znaków na datę musimy skorzystać z klasy NSDateFormatter. Przykładowo, jeśli chcemy przekształcić ciąg „24/12/2013” na datę możemy zrobić to tak.



NSDateFormatter* dateFmt = [[NSDateFormatter alloc] init];
[dateFmt setDateFormat:@"MM/dd/yyyy"];
NSDate* date = [dateFmt dateFromString:@"24/12/2013"];

 

Więcej na temat dat, konwersji i formatowania można znaleźć w dokumentacji Apple:

  1. Date and Time Programming Guide
  2. Data Formatting Guide

 

Warto tez zerknąć na Unicode Technical Standard (UTS) i zdefiniowane tam wzorce formatowania dat.



piątek, 31 stycznia 2014
Praca z Windowsem na MacBook-u (brakujące klawisze)

 

Ostatnio coraz więcej i więcej czasu spędzam pracując z systemem Windows zainstalowanym na MacBook-u i zaczęło mi brakować dobrze znanych ze standardowej klawiatury funkcji (klawiszy) takich jak Windows, Delete, Page Up, Page Down, Insert itp. Dla niezorientowanych klawiatura MacBooka jest zdecydowanie nieprzystosowana dla osób przyzwyczajanych do pracy z Windows. 

xxx

Poniższa tabelka przedstawia najczęściej wykorzystywane funkcje dostępne na standardowych klawiaturach i odpowiadające im klawisze / kombinacje klawiszy dla Windows oraz dla MacBook-a.

 

Funkcja Windows

Klawisz / kombinacja klawiszy na klawiaturze Windows

Odpowiadający klawisz / kombinacja klawiszy na klawiaturze MacBook-a

 

Windows

 

Logo Windows

 

Command (⌘)

Delete

Delete / Del

Fn + Command + Backspace

Ctrl + Alt + Delete

Ctrl + Alt + Delete

Fn + Command + Alt + Backspace

Print Screen

Print Screen

Fn + Shift + F11

Print Screen (tylko aktywne okno)

Alt + Print Screen

Fn + Shift + Option +  F11

Insert

Insert

Fn + Enter

Page Up

Page Up

Fn + Cursor Up ()

Page Down

Page Down

Fn + Cursor Down()

Home

Home

Fn + Cursor Left ()

End

End

Fn + Cursor Right (→)



Więcej funkcji i odpowiadające im kombinacje klawiszy znajdziecie na stronach Apple w wersji angielskiej oraz polskiej.

 

środa, 22 stycznia 2014
Programowanie iPhone - Base SDK a Deployment Target

 

Tworząc oprogramowanie na platformę Apple bazuje wcześniej, czy później spotkasz się z pojęciem programowaniu opartego o SDK (ang. SDK-based development). Bazuje ono na pojęciach Base SDK  oraz Deployment Target.

 

Base SDK – z reguły jest to najnowsza dostępna wersja systemu iOS / Mac OS i sygnalizuje, że nasza aplikacja może wykorzystać w pełni możliwości określonej tu wersji systemu.

Deployment Target – najstarsza wspierana przez nasz program wersja systemu iOS / Mac OS, na której nasz program może być zainstalowany i uruchomiony.

 

Przykładowo jeśli jako Base SDK wybierzemy iOS 6 a jako deployment target iOS 5, program nasz będzie mógł być zainstalowany na urządzeniach działających pod kontrola systemu iOS 5 i nowszych. Możemy jednak wykorzystać możliwości oferowane przez iOS 6, musimy być jednak ostrożni podczas wykorzystywania specyficzne możliwości iOS 6 i pamiętać o użytkownikach, którzy będą korzystali z naszego programu na starszych urządzeniach, z zainstalowanym systemem iOS 5.

 

Więcej szczegółowych informacji na ten temat można znaleźć w dokumentacji Apple – SDK Compatibility Guide.



 
1 , 2 , 3 , 4 , 5 ... 16
Tagi




PowerBuilder Tetris
D - Tetris



Programowanie iOS

C# ToolBox

SQL / TSQL / PLSQL ToolBox

Linux / Unix ToolBox





Zaprzyjaznione Strony

Sprite Bandits

Cake Time