[Из песочницы] Использование блоков в iOS. Часть 1

В процессе изучения Objective-C и iOS-разработки не могла понять принципы работы блоков. С толку сбивало, что их можно передавать как параметры в методы. Наткнулась на статью, которая показалась мне крайне интересной, так как рассматривались не только блоки, но и процесс разработки приложения. Пост адаптирован под xCode 7.3.1.

Предисловие


Блоки — невероятно мощное добавление к C/Objective-C, позволяющее «обернуть» куски кода в отдельные единицы и оперировать ими в качестве объектов. Все больше и больше API требуют использование блоков в iOS, поэтому очень важно их понимать. Однако, их синтаксис и некоторые тонкие аспекты зачастую сбивают с толку начинающих. Но бояться не стоит — этот урок будет весьма полезным.

В двух частях этого урока мы создадим небольшой iOS проект «iOS Diner». Приложение само по себе простое: пользователь выбирает блюдо из меню для создания заказа, как будто бы собираясь пообедать. Осторожно: в процессе создания обостряется чувство голода!

В первой части разработаем UI нашего приложения, заодно рассмотрев Storyboard (дословно — раскадровка — Прим. пер.), включая небольшую памятку по созданию и использованию веб-сервисов для загрузки меню в формате JSON.

Примечание. Если вы уже довольно хорошо разбираетесь со Storyboard и Interface Builder, можете пропустить первую часть и сразу перейти ко второй части, где мы начнем использовать блоки. Эта часть фокусируется только на Storyboard и Interface Builder.

Во второй части будет показано всестороннее использование блоков для программирования логики приложения, асинхронной обработки данных, фоновых задач, использования в качестве альтернативы многим стандартным API, и многое другое.

Начинаем


Открываем Xcode и создаем новый проект.

image

В названии проекта укажите «iOSDiner».

b1aaa1d211354fa0bbceff3556fb7705.png

Запустите проект. Конечно, он еще пустой, и на симуляторе будет пустой белый экран.

03c3fe38db5246faad22d3a64b8e3b63.png

В настройках проекта в Deployment Info убираем галочку Portrait.

c89bc8a39afd46f09095f6191a33d36a.png

Первое, что мы сделаем, — настроим представление. Для этого понадобится немного графики. Скачать можно отсюда. Это нужно будет добавить в ресурсы проекта.

Если честно, мне никогда не нравилось, как xCode обрабатывает совпадение файлов в проекте и файловой системе, поэтому обычно я добавляю ресурсы вручную в файловой системе. В Finder откройте папку проекта и в ней создайте папку «Resources». В ней создайте папку «Images».

58a42058603b4982b8ec10e0a773f687.png

Скопируйте графику из загруженного ZIP-файла в папку Images, затем перетащите папку Resources в папку IOSDiner в Xcode, как показано на скриншоте ниже.

6ac8041ea4fe46119ce17782c06c22a2.png

Теперь можно увидеть папку Resources в Xcode, в которой есть подпапка Images с загруженными изображениями, — как и в файловой системе.

Добавление изображений


Откройте Main.storyboard.

image

Если вы не видите вторую колонку с названием »View Controller Scene», внизу кликните на кнопку Expand.

Итак, мы собираемся добавить изображения в Storyboard в виде UIImageViews и UIButtons. Чтобы было проще, откройте боковую панель Utilities и выберите Media Library.

71604283ec414fb3a5684387c7cde646.png

Здесь мы видим все раннее добавленные в проект изображения. Наверняка вы заметили, что у каждой картинки есть своя копия с »@2x» в конце названия. Она используется для retina-версии.

Мы заинтересованы только в обычной картинке. Вы можете проверить, какая это версия картинки, кликнув и нажав клавишу пробел. Перетащите «bg_wall.png» на root-view, как показано ниже. Если вы не уверены, что изображение поставлено корректно, можете переключиться в Size Inspector для изменения его X и Y координат.

2f01d404d3f943778ffbe27ecace1a07.png

123686ce362744178eb91b1bf67ff2fb.png

Теперь проделайте то же самое со следующими изображениями:

  • person.png
  • sign_theiOSdiner.png
  • chalkboard.png
  • bg_counter.png
  • total_field.png
  • food_box.png

Примечание. Вы можете заставить Image View соответствовать точным размерам картинки, нажав одновременно Cmd и клавишу =. Расставив все картинки на свои места, запустите проект.

8a40526ba4cd442d83cd7776bd8031b7.png

Вау! Почти похоже на приложение! Далее, добавим части пользовательского интерфейса. На панели Utilities переключитесь на Object Library.

Перетащите Button в центр нашего представления, над монитором. Дважды кликните на только что добавленную кнопку и напишите »-1».

7136dda86d694bc9a902e7b42bd313a1.png

Round Rect Button
В оригинале использовались Round Rect Button, которые отсутствуют в xCode 7.3.1. На Stack Overflow есть решение, как их использовать.

Убедитесь, что кнопка выделена. В Attributes Inspector установите атрибут Background «button_silver.png».Нажмите и удерживайте клавишу Alt и перетащите кнопку -1 вправо. Так создастся копия объекта. Измените ее текст на »+1».

09bfd94d5c2d4f788c774b1f8c5b4d86.png

70d7c9c694fb4130b9c84137f46a1024.png

Перетащите еще одну кнопку на левый край монитора. Установите Button Type в Custom, удалите заголовок кнопки и из Media Library перетащите на кнопку «button_arrow_left.png»

b2ff640403954c1d8cb770e1746b4a74.png

Скопируйте эту кнопку и поменяйте фоновое изображение на «button_arrow.png»

61722f2ae280444484923e2ea4d11111.png

И осталась последняя кнопка. Разместите ее под доской и установите фоновое изображение «total_field.png». Запустите проект.

b9722dd89bc044d285b23f40759f7e24.png

Выглядит вполне симпатично. Следующее, что мы добавим, — лейблы и окошко предварительного просмотра.
Снова идем в Object Library. Перетаскиваем UILabel на доску и растягиваем его по размеру доски.

f7ebe2f835c9496ca7c7d38ad52865ec.png

В Atrributes Inspector установите Lines в 0 (это сделает лейбл многострочным), поменяйте Text Color на белый, и Font — на Marker Felt 17.0. Обычно использование Marker Felt я считаю преступлением, но в нашем случае он вполне подходит.
1fc12c5c8ee748bbb4e2bab580e6a0f2.png

Перетащите UIImageView на монитор и растяните по его размеру.

6803d36132f24262977b239ab164c076.png

В Attributes Inspector поменяйте Mode на Aspect Fit.

916be1f292064a02b804d3b0fe80bf13.png

Перетащите еще один UILabel на табличку в правом нижнем углу. Сделайте его по размеру серой зоны таблички и установите Alignment в Center.

25976b2f5a9143bfac667cea9773fc52.png

Устанавливаем IBOutlets и IBActions


Далее необходимо установить связи между пользовательским интерфейсом, который мы только что создали, и кодом. Для этого и нужны IBOutlets и IBActions. IB означает Interface Builder, который используется для создания UI в xCode.
  • IBOutlet, в основном, — это связь элемента UI (кнопки или лейбла) с его ссылкой в коде.
  • IBAction — это действие (или метод, как удобнее) в коде, которое можно подключить к некоторому событию (нажатие кнопки, например) в разработанном интерфейсе.

Итак, давайте напишем немного кода для подключения элементов UI через IBOutlets и IBActions.

Закройте вкладку Utilities и откройте Assistant editor. В зависимости от того, как он настроен, экран может выглядеть не так, как на скриншоте. Если вы хотите изменить отображение Assistant editor, загляните в его меню.

0d6774891a9b43ff881571dc9e2870f5.png

Начнем с кнопок. Выберите кнопку »-1», удерживайте клавишу Ctrl и перетащите ее в код. Это автоматизирует создание IBOutlet для кнопки.

Для этого объекта все, что нужно, — назвать его. Мне нравятся префиксы «ib», так как в xCode становится легко найти все элементы по автозаполнению. Назовите этот объект «ibRemoveItemButton» и кликните Connect.

aad69cfadd9d4eeea02df341462ff7c5.png

Аналогично делаем с остальными кнопками.

7754176730b24758bbb8673b3ff73551.png

5d86cdc6845c4fa1801a4e396faa737b.png

811cf9585c6e496ca214253108558e09.png

55be75a1c39b4232bd405971acc8bffb.png

Точно так же устанавливаем лейблы.

da3985b2829247dba28477000cda70bd.png

79080b9c903740b09fd0b59fd2625c39.png

00e1f76a7c9b4ac3a108840f8901b213.png

Теперь ViewController.m должен выглядеть так:

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *ibRemoveItemButton;
@property (weak, nonatomic) IBOutlet UIButton *ibAddItemButton;
@property (weak, nonatomic) IBOutlet UIButton *ibPreviousItemButton;
@property (weak, nonatomic) IBOutlet UIButton *ibNextItemButton;
@property (weak, nonatomic) IBOutlet UIButton *ibTotalOrderButton;

@property (weak, nonatomic) IBOutlet UILabel *ibChalkboardLabel;
@property (weak, nonatomic) IBOutlet UIImageView *ibCurrentItemImageView;
@property (weak, nonatomic) IBOutlet UILabel *ibCurrentItemLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Теперь мы должны добавить IBActions для UIButtons. Это методы, которые вызываются в ответ на определенные события (Touch Up Inside, Touch Up Outside, Touch Cancel, и т.д.). Для кнопок чаще всего используется Touch Up Inside.

Выберем кнопку »-1». Снова с помощью клавиши Ctrl перетаскиваем ее в ViewController.m. Так как это IBAction, используем префикс «iba». Повторяем для всех кнопок.

7055ff8b8e1a4a47b9658016687446b7.png

31801e2bebfc4891b645c38af16763a3.png

800559b736ee4a75b584f0ccd47b2c90.png

68373d16eb8f40d888a66e9589b7d3c5.png

04b3bd71675d4bdaa5bfd1a118be0103.png

Устанавливаем веб-сервис


Прежде чем приступить к коду, настроим веб-сервис. Я не собираюсь объяснять все в мельчайших подробностях, так как для этого уже существует несколько уроков (How To Write A Simple PHP/MySQL Web Service for an iOS App и How to Write an iOS App That Uses a Web Service).

Код ниже показывает, как будет выглядеть PHP код для веб-сервиса.

 'Continue', 
        101 => 'Switching Protocols', 
        200 => 'OK', 
        201 => 'Created', 
        202 => 'Accepted', 
        203 => 'Non-Authoritative Information', 
        204 => 'No Content', 
        205 => 'Reset Content', 
        206 => 'Partial Content', 
        300 => 'Multiple Choices', 
        301 => 'Moved Permanently', 
        302 => 'Found', 
        303 => 'See Other', 
        304 => 'Not Modified', 
        305 => 'Use Proxy', 
        306 => '(Unused)', 
        307 => 'Temporary  Redirect', 
        400 => 'Bad Request', 
        401 => 'Unauthorized', 
        402 => 'Payment Required', 
        403 => 'Forbidden', 
        404 => 'Not Found', 
        405 => 'Method Not Allowed', 
        406 => 'Not Acceptable', 
        407 => 'Proxy Authentication Required', 
        408 => 'Request Timeout', 
        409 => 'Conflict', 
        410 => 'Gone', 
        411 => 'Length Required', 
        412 => 'Precondition Failed', 
        413 => 'Request Entity Too Large', 
        414 => 'Request-URI Too Long', 
        415 => 'Unsupported Media Type', 
        416 => 'Requested  Range Not Satisfiable', 
        417 => 'Expectation Failed', 
        500 => 'Internal  Server Error', 
        501 => 'Not Implemented', 
        502 => 'Bad Gateway', 
        503 => 'Service Unavailable', 
        504 => 'Gateway Timeout', 
        505 => 'HTTP Version Not Supported'
    );
 
    return (isset($codes[$status])) ? $codes[$status] : '';
}
 
 // Helper method to send a HTTP response code/message
function sendResponse($status = 200, $body = '', $content_type = 'text/html') { 
    $status_header = 'HTTP/1.1 ' . $status . '     ' . getStatusCodeMessage($status);
    header($status_header);
    header('Content-type:     ' . $content_type);
    echo $body;
}
 
class InventoryAPI {
    function getInventory() {
        $inventory = array(
            array("Name"=>"Hamburger","Price"=>0.99,"Image"=>"food_hamburger.png"),
            array("Name"=>"Cheeseburger","Price"=>1.20,"Image"=>"food_cheeseburger.png"),
            array("Name"=>"Fries","Price"=>0.69,"Image"=>"food_fries.png"),
            array("Name"=>"Onion Rings","Price"=>0.69,"Image"=>"food_onion-rings.png"),
            array("Name"=>"Soda","Price"=>0.75,"Image"=>"food_soda.png"),
            array("Name"=>"Shake","Price"=>1.20,"Image"=>"food_milkshake.png")
        );
 
        sendResponse(200, json_encode($inventory));
    }
}
 
sleep(5);
 
$api = new InventoryAPI;
$api->getInventory();
?>

Этот веб-сервис представляет собой вполне простой PHP-скрипт, который возвращает JSON-массив. В массиве находится так называемый ассоциативный массив (PHP), или словарь(Objective-C), в котором содержится название, цена и имя изображения для предмета. Единственное, что следует отметить в вышеуказанном коде, — это строчка sleep(5);.

Я использую ее для симуляции медленного веб-сервиса, чтобы лучше показать, как блоки могут помочь с асинхронными операциями.

Вы можете скопировать этот код в файл с расширением .php и разместить его на хостинге, или просто используйте мой.

Что делать дальше


Проект этой части урока можно скачать здесь. Что ж, здесь было много всяких вещей, никак не связанных с блоками. Но сейчас, установив view и веб-сервис, мы можем перейти к кодингу, где и будем использовать блоки, чтобы сделать наше приложение полностью функционирующим.

Комментарии (3)

  • 12 сентября 2016 в 16:50

    0

    Слишком много инфомации о блоках в одной статье. Вам нужно разбить хотя бы на две.
    • 12 сентября 2016 в 16:51

      0

      Статья уже разбита на две части — вторая пока что в процессе.
  • 12 сентября 2016 в 17:03

    +1

    Почему не написали что в этой статье о блоках ни слова не будет сказано?

© Habrahabr.ru