Разработка iOS8 приложения на Apple Swift. Ios swift


Разработка iOS 10 приложений с помощью Swift / Хабр

Стэнфордский университет, США — один из лучших в мире в области информатики (Computer Science). Он щедро делится своими курсами, и одним из самых популярных и успешных курсов является курс CS193P по разработке приложений на iOS, который читает профессор Пол Хэгерти. Это курс читается ежегодно, начиная с 2010 года. На данный момент актуальным является курс CS193P «Developing iOS 10 Apps with Swift», Зима 2017 года — «Разработка iOS 10 приложений с использованием Swift», и уже выложены все 17 Лекций и 5 Заданий на iTunes U (но можно смотреть и на Youtube). В новом курсе отчетливо видно, что профессор сделал небольшой разворот всего курса от ОБЪЕКТНО-ОРИЕНТИРОВАННОГО программирования, к ФУНКЦИОНАЛЬНОМУ программированию с множеством неизменяемых объектов с APIs похожими на математические функции. Конечно, iOS вовсе не была сконструирована на основе идеи функционального программирования. Но люди, которые изобретали Swift, держали “в голове” идею о функциональном программировании. Так что при разработке вашего iOS приложения (по крайней мере в части Модели) вы можете использовать множество элементов функционального программирования.

Это новый рубеж в iOS программировании, потому что в течение 10 лет люди, в основном, использовали в iOS программировании только reference type объекты, имея в виду концепцию объектно-ориентированного программирования. Но когда вы программируете на Swift с помощью структур structs и перечислений enums, и особенно если вы подключаете механизмы протоколов protocols и Generics, то вы можете реально сделать прекрасную работу, имеющую дело с реальным функциональным программированием. И начиная прямо с Лекции 1 и Задания 2 нового курса акценты расставлены совсем по-другому. Теперь на первом месте value types, а о reference types — лишь мимоходом, подробно о структурах structs и перечислениях enums, а о классах classes — лишь мимолетом. Очень подробно о диапазонах Range&ltT&gt и о том, при каких условиях Range&ltT&gt становится последовательностью Sequence и позволяет применять к себе синтаксическую конструкцию for in.

Начинает профессор с очень подробного рассказа о синтаксисе Swift 3 и о возможностях Xcode 8. Буквально объясняет каждый символ. Далее он показывает как проектировать Модель с использованием структуры struct, отталкиваясь от public API Модели, как добиться расширяемой функциональности в структурах struct, ведь мы не можем использовать создание subclass, как в классе class. Показано создание вложенных структур struct и перечислений enum. На очень коротких фрагментах кода объясняется, когда стоит применять Optional, а когда — нет, исходя из семантического содержания Optional. Большое внимание уделено перечислениям enum, имеющим ассоциированные значения любого типа, в том числе и замыкания (closure). Рассматриваются такие возможности свойств как наблюдатели willSet{} и didSet {} и отложенная инициализация lazy. Поэтому чрезвычайно полезен для начинающих разработку iOS приложений на Swift, но не для начинающих изучать программирование как таковое.

Особый прорыв в изучении программирования на Swift в операционной системе iOS удается достичь при выполнении Заданий, которые предлагаются на этом курсе. Они, как правило, очень интересные, и вы, несомненно, получите удовольствие от их выполнения. Но они также потребуют от вас знание всего спектра приемов работы в Swift со структурами struct, перечислениями enum, семантическое понимание Optional, использование замыканий, кортежей и т.д, а также утонченное знание iOS 10: «жизненный цикл» View Controller , многопоточность, Scroll View , Table View , Collection View , Core Data , Dynamic Animation и т.д.

Уже 3 года существует сайт, который помогает вам пройти этот замечательный курс до конца и выполнить все Задания этого курса. Задача данного проекта в том, чтобы вывести вас, как разработчика, на достаточно высокий уровень iOS программирования, когда вы свободно сможете общаться с экспертами и понимать все, что говорится на WWDC, а также следить за созданием Swift 4 на сайте Swift.org, а не “болтаться” вечно в обучающих материалах. После качественного самостоятельного программирования Заданий курса CS193P никакие обучающие курсы уже будут не нужны.

На этом сайте выложены все необходимые материалы для изучения стэнфордских курсов: неавторизованные русскоязычные конспекты лекций, демонстрационные примеры, Задания на русском языке и варианты решений Заданий для iOS 10 и Swift 3. Процесс выполнения заданий на Mac в Xcode 8 — очень увлекательный: хорошая информационная поддержка, результат отображается на симуляторах iPhone и iPad или на реальных устройствах, визуализация процесса отладки.

Самое интересное заключается в том, что из Xcode 8 вы можете запустить демонстрационные примеры и выполненные вами Задания на своем устройстве (или поделится с друзьями) совершенно бесплатно — для этого не нужно иметь сертификат разработчика Developer Account, стоимостью 99 $/год. Начиная с Xcode 7 политика Apple по отношению к разработчикам изменилась: они стараются привлечь к разработке приложений более молодых программистов.

Если вы являетесь опытным программистом на каком-то другом языке (Java, Python, C++), то, как показывает опыт, вам достаточно 2-3 недель, чтобы разобраться с синтаксическими конструкциями Swift ( у него очень короткий период вхождения). А потом — добро пожаловать на стэнфордский курс «Developing iOS 10 Apps with Swift», чтобы попробовать свои силы на выполнении Заданий, текст которых представлен на русском и английском языках здесь. Первые три Задания не связаны ни с многопоточностью, ни с таблицами Table View, ни с базами данных Core Data, ни с анимацией. Это чистая практика на знание Swift и MVC. Здесь вам придется управлять хранением данных в UserDefaults с помощью вычисляемых переменных с кодом в пару строк, понять, что захватывают замыкания (closure), и как «разорвать» циклическую ссылку памяти с помощью weak и unowned в списке «захвата», что такое @escaping функции, как работать с кортежами и т.д. На Github есть примеры решения Заданий. Так что стоит попробовать и использовать все свои навыки в программировании на Swift. Кроме того, можно сравнить свое решение с уже имеющимся. Имеются решения Заданий:Задание 1 cs193p Winter 2017 Калькулятор.Решение. Обязательные и дополнительные пункты.Находится на Github.

Задание 2 cs193p Winter 2017 «Умный» Калькулятор. Решение. Обязательные и дополнительные пункты. Находится на Github.

Задание 3 cs193p Winter 2017 «Графический» Калькулятор. Решение. Обязательные пункты. Находится на Github.

Задание 3 cs193p Winter 2017 «Графический» Калькулятор. Решение. Дополнительные пункты. Находится на Github.

Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — обязательные пункты 1- 7. Находится на Github.

Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — обязательные пункты 8 — 10. Находится на Github.

Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — дополнительные пункты 1-5. Находится на Github.

Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — дополнительный пункт 6. UICollectionView и перемещение ячеек. Находится на Github.

Задание 4. CS193P Winter 2017. Smashtag Mentions (клиент Twitter). Решение — дополнительный пункт 6. UICollectionView с расположением ячеек типа WaterFall и переключение между Layouts. Код находится на Github.

P.S.ОБСУЖДЕНИЕ МАТЕРИАЛОВ курса «Разработка iOS приложений с Swift» проводится на private форуме на Piazza. Вопросы можно задавать там. Для регистрации вам необходимо пройти по ссылке:piazza.com/moscow_physical_engineering_institute_bestkora.com/spring2017/mf141 и набрать private код mf141.

habr.com

Разработка iOS8 приложения на Apple Swift / Хабр

Статья является своеобразным продолжением статьи «Знакомьтесь, Swift!» за авторством Helecta, а также вольным переводом статьи Developing iOS Apps Using Swift Tutorial Part 2.

Итак, в первой статье мы написали простое Single View приложение, включающее таблицу с несколькими ячейками. На этот раз мы немного углубимся и сделаем несколько более амбициозных вещей. Мы будем обращаться к API поиска iTunes, парсить ответ, полученный в JSON и отображать результаты в Table View. На первый взгляд может показаться, что все это довольно сложно и предстоит много работы, но на самом деле это не так. Все описанное выше является достаточно простым функционалом для iOS приложений и каждый уважающий себя iOS разработчик должен это уметь. Нам понадобится Single View Application c добавленным Table View. Останавливаться на этом не будем, так как все это достаточно просто описано в первой статье.

Подключение интерфейса
Для начала, нам нужно получить указатель на наш Table View для того чтобы использовать его в коде приложения. Отправляемся в файл ViewController.swift и сразу в инициализации класса (class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {) добавляем следующую строчку:@IBOutlet var appsTableView : UITableView Это позволит нам ассоциировать Table View в Storyboard с переменной «appsTableView». Переходим к Storyboard. Кликаем по View Controller с зажатым control и тянем курсор к Table View, тем самым связывая эти объекты. В появившейся менюшке Outlets выбираем «appsTableView».
Выполнение API запроса
Теперь, после того как мы подключили интерфейс, можно выполнять наши API запросы. В файле ViewController.swift внутри инициализации класса ViewController создаем функцию searchItunesFor(searchTerm: String).func searchItunesFor(searchTerm: String) { // Для The iTunes API слова в поисковом запросе должны быть разделены при помощи "+", поэтому нам необходимо произвести соответствующие замены. var itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil) // Помимо этого, необходимо удалить все что не URL-friendly var escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) var urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software" var url: NSURL = NSURL(string: urlPath) var session = NSURLSession.sharedSession() var task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in println("Task completed") if(error) { println(error.localizedDescription) } var err: NSError? var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary if(err?) { println(error.localizedDescription) } var results: NSArray = jsonResult["results"] as NSArray dispatch_async(dispatch_get_main_queue(), { self.tableData = results self.appsTableView.reloadData() }) }) task.resume() }

Давайте по порядку. Исправляем наш поисковый запрос, чтобы iTunes API получил текст вида «First+Second+Third+Words» вместо «First%20Second%20…». Для этого мы используем доступный в NSString метод stringByReplacingOccurencesOfString, который заменяет пробелы на "+". Далее, мы очищаем полученную строку от символов, которые не поддерживаются URL. Следующие две строчки определяют объект NSURL, который будет использоваться в качестве запроса к API. Обратим внимание на следующие две строчки:

var session = NSURLSession.sharedSession() var task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in Первая берет стандартный объект NSURLSession, который используется для сетевых вызовов. Вторая создает задание, которое и посылает запрос. Наконец, строчка task.resume() начинает выполнение запроса.
Подготовка к получению ответа
Мы получили метод, который при вызове выполняет поиск в iTunes. Вставим его в конце метода viewDidLoad в нашем ViewController.override func viewDidLoad() { super.viewDidLoad() searchItunesFor("Angry Birds") } Теперь, для того чтобы получить ответ, необходимо добавить объект, который будет хранить результаты поиска. Поэтому, добавим инстанс NSMutableData и массив NSArray, для хранения данных для нашей таблицы (внутри инициализации класса ViewController, например сразу после указателя @IBOutlet var appsTableView: UITableView).var data: NSMutableData = NSMutableData() var tableData: NSArray = NSArray() Теперь, давайте объединим функции, которые NSURLConnection будет отправлять в наш класс. Так как это делегат нашего запроса, любая информация от NSURLConnection будет возвращаться методами протокола, определенными в NSURLConnectionDataDelegate и NSURLConnectionDelegate. Поэтому, в инициализации ViewController укажем также NSURLConnectionDelegate, NSURLConnectionDataDelegate, будет что-то вроде:class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSURLConnectionDelegate, NSURLConnectionDataDelegate {
Получение ответа
Нам предстоит добавить самый большую часть кода нашего приложения для обработки полученной информации.func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) { self.data = NSMutableData() } func connection(connection: NSURLConnection!, didReceiveData data: NSData!) { self.data.appendData(data) } func connectionDidFinishLoading(connection: NSURLConnection!) { var err: NSError var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary if jsonResult.count>0 && jsonResult["results"].count>0 { var results: NSArray = jsonResult["results"] as NSArray self.tableData = results self.appsTableView.reloadData() } } Когда NSURLConnection получает ответ, вызывается метод didReceiveResponse. Тут мы просто сбрасываем наши данные, если они были, прописывая self.data = NSMutableData() для создания нового объекта. После установки соединения, мы начинаем получать данные в методе didReceiveData. Здесь передается аргумент data: NSData, где и находится вся интересующая нас информация. Нам нужно сохранить все полученные в ответе части, поэтому мы присоединяем их к объекту self.data, созданному выше. Наконец, после того как мы в ответе получили всю информацию, вызывается метод connectionDidFinishLoading, где мы уже можем начать использовать результат. Мы будем использовать класс NSJSONSerialization для преобразования необработанных данных в полезную информацию в виде объектов словаря NSDictionary. Теперь, когда мы убедились что получен какой-то ответ от iTunes, простой проверки ключа «results» будет достаточно для того чтобы удостовериться что мы получили именно то что ожидали, поэтому мы можем установить объект self.tableData равным results, а также обратиться к методу таблицы appsTableView.reloadData() для обновления ее содержимого.
Обновление интерфейса Table View UI
Для инициализации Table View в первой статье, нам необходимо было определить две функции: одна возвращает количество строк в таблице, вторая создавала ячейки и описывала их содержимое. Обновим эти функции, в соответствии с новым функционалом.func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { return tableData.count } func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell") var rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary cell.text = rowData["trackName"] as String // Обращаемся к ключу artworkUrl60 для получения ссылки на обложку объекта var urlString: NSString = rowData["artworkUrl60"] as NSString var imgURL: NSURL = NSURL(string: urlString) // Скачиваем файл обложки в объект NSData для последующего отображения в ячейке var imgData: NSData = NSData(contentsOfURL: imgURL) cell.image = UIImage(data: imgData) // Получаем цену объекта по ключу formattedPrice и отображаем ее в качестве subtitle var formattedPrice: NSString = rowData["formattedPrice"] as NSString cell.detailTextLabel.text = formattedPrice return cell } Функция numberOfRowsInSection теперь просто возвращает количество полученных в ответе объектов. Функция cellForRowAtIndexPath вместо отображения номера строки теперь отображает название объекта, его обложку и стоимость. Если все хорошо и у вас получилось запустить приложение, возможно вы заметите некоторые «лаги» во время его работы. Это связано с тем, что мы не предусмотрели несколько вещей, которым уделим внимание в следующей статье.

P.S.: После выхода моего перевода, автор несколько обновил урок, немного оптимизировав код. Перевод я постарался привести в соответствие.

habr.com

Разработка iOS на Swift в примерах на русском

iOS-Swift-LogoСайт, который Вы сейчас открыли, был создан с целью собрать знания по теме программирование в среде iOS. Программирование под iOS будет осуществляться в среде разработки Xcode, на языке программирования Swift, который Apple представила широкой общественности в 2014 г. Для начала я буду складывать на сайте все полезности с примерами. Но если у блога появятся постоянная аудитория, то статьи  на сайте будут появляться, согласно мнения общественности.

Итак, для кого же этот сайт? Сайт рассчитан на пользователей, которые немного понимают синтаксис Swift, делали первые попытки Swift программирования или пытались найти ресурсы, где бы присутствовала  документация Swift на русском. Однако на сайте также присутствует раздел для программистов, которые хотят изучить  программирование на swift с нуля. В этом разделе собраны ссылки и материалы, которые по мнению редакции, помогут освоить тонкости разработки iOS приложений.

Хотелось бы обратить внимание на несколько моментов…

Если вы разрабатываете приложения iOS или ищите документацию Apple Swift, то хорошо понимаете, что основная масса информации на английском языке. Исторически сложилось, что для изучения документации нужно знать английский. Наша редакция очень сильно постарается размещать материалы, документацию Swift на русском. Однако если будет интересный материал, но не будет альтернативы на русском языке, то ссылка на этот материал все же будет размещена, но будет отдельно помечена.

Статьи в блоге условно можно разделить на две части: теория и фундаментальные основы iOS Swift, и реальные примеры, которые проверены в системе разработки Xcode. Для отбора статей с примерами можно выбрать рубрику, которая так  и называется «Реальные примеры«

Критику и предложения можно оставлять в комментариях. Наша редакция всегда готова поучится чему-то новому и открыта для сотрудничества.

С уважением, команда proSwift.ru

proswift.ru

Учебник Swift — разработка приложения для iOS8 [Часть 1, Hello World!] / Хабр

Предисловие
Недавно Apple представила общественности достаточно важное изменение в разработке iOS приложений, анонсировав новый язык программирования Swift. Я принял решение: изучая этот язык пошагово, я буду в своих статьях рассказывать обо всём, что мне удалось найти. Это лишь первый пост из многих на эту тему, но я надеюсь, что вы решите изучать язык вместе со мной!

Весьма вероятно, что многие примеры кода, представленные в постах, будут позже изменены. Это отчасти потому, что мой стиль программирования – написать сейчас, чтобы проверить идею, а потом заняться рефакторингом, а также отчасти это потому, что для меня (как и для многих других) Swift – абсолютно новый язык программирования. Так что, скорее всего, как только я узнаю что-нибудь новое, ход этих уроков будет меняться.

Итак, я собираюсь начать с довольно простого приложения. Также я буду объяснять, как работает код. Готовы? Поехали…UPD: Статья написана в соответствии с изменениями в XСode 6 Beta 5

Основы
Swift отменяет использование стандарта объявления переменных, который использует имя типа перед объявлением переменной, вместо этого для объявления, как и в JavaScript, применяется ключевое слово var. Так, например, эта строка Objective-CNSString *myString = @"This is my string."; в Swift будет заменена на этуvar myString = "This is my string." Для объявления констант используется ключевое слово letlet kSomeConstant = 40 В данном случае kSomeConstant неявно определяется как целое число. Если же вы хотите конкретизировать тип, то вы можете это сделать так:let kSomeConstant: Int = 40 Немножко о Чистом КодеАвтор перевода рекомендует пользоваться вторым примером объявления констант. Довольно показательный пример описан в официальной документации:let implicitInteger = 70 let implicitDouble = 70.0 let explicitDouble: Double = 70 В этом автор перевода солидарен с Helecta (см. соответствующий пост) И массивы, и словари описываются с помощью []var colorsArray = ["Blue", "Red", "Green", "Yellow"] var colorsDictionary = ["PrimaryColor":"Green", "SecondaryColor":"Red"] Это ещё далеко не всё, однако я считаю, что эти основы достаточно важны для дальнейшего чтения учебника. Итак, давайте перейдём к Hello, World!
Hello, World!
В первую очередь, мы напишем самое простое приложение, которое только можно представить, чтобы начать работу — Hello, World! Наше приложение будет делать только одно: печатать фразу «Hello, World!» в консоль. Для этого нам потребуется установленная IDE XCode, для скачивания которой необходима учётная запись разработчика. Если у вас есть аккаунт, то смело качайте с официального сайта XCode 6 beta 4, желательно это сделать до прочтения ниже описанного. Итак, вы установили свою копию IDE. Теперь давайте выведем «Hello, World!» на консоль.Этот пример не только демонстрирует простейшее приложение, которое можно написать, но и, что немаловажно, показывает, что среда разработки настроена правильно. В XCode создайте проект с помощью шаблона приложения с одним видом («Single View Application»). Убедитесь, что вы выбрали Swift в качестве языка приложения. Теперь вы должны найти файл AppDelegate.swift в иерархии проекта. Внутри найдите следующую строку:"// Override point for customization after application launch." Замените эту строку на наш изумительный код:println("Hello World") Теперь нажмите «Run». Вы должны увидеть загрузившееся пустое приложение и слова Hello, World!, напечатанные в консоли, расположенной в нижней части окна XCode. Заметьте, это не будет отображаться в симуляторе iPhone. Поздравляю! Вы только что написали своё первое приложение на Swift! Правда оно не получит никаких премий, призов, только ваши собственные овации. А теперь, давайте копнём немножко глубже…
Добавление Table View
В этом разделе мы попробуем добавить материал на экран. Откройте в XCode файл Main.storyboard, перенесите из Библиотеки Объектов («Object Library») объект Table View на экран приложения, растяните таблицу так, чтобы она совпала с краями. Затем измените высоту, перетянув верхний край, оставив небольшое пространство сверху (это необходимо для строки состояния). Если вы запустите приложение, то увидите в симуляторе пустую таблицу. Теперь необходимо создать делегат и источник данных для таблицы. Это легче всего сделать в конструкторе интерфейса. Нажмите клавишу «Command», кликните и перетащите Table View к объекту View Controller в иерархии файла .storyboard, и выберите «источник данных» («data source»). Повторите с опцией «delegate».

Примечание:Я получил целую тонну вопросов о вышесказанном, т.к. многие люди сообщают о том, что у них не устанавливается табличный вид. Поэтому, я записал видео, поясняющее как работает подключение объектов Storyboard в коде. Для просмотра перейдите в полноэкранный режим и выберите опцию 720p, чтобы вы могли заметить всё, что вас интересует. Это будет немного отличаться от интерфейса XCode, но функционально всё работает также.

А теперь давайте углубимся в методы протоколов для табличного представления. Из-за использования UITableViewDataSource и UITableViewDelegate мы должны изменить определение класса. Откройте файл и замените строку

class ViewController: UIViewController { следующейclass ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { Если вы нажмёте клавишу «Command» и кликните на один из этих протоколов, то увидите «обязательные» функции. Для табличного представления необходимы как минимум эти две:func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! Изменим наш класс View Controller путём добавления этих двух функций:func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { return 10 } func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell") cell.textLabel.text = "Row #\(indexPath.row)" cell.detailTextLabel.text = "Subtitle #\(indexPath.row)" return cell }

Первый метод возвращает количество строк в разделе. В нашем примере используется «магическое» число 10, однако вместо него, как правило, должна использоваться длинна контроллера массива. Наш же код умышленно упрощён.

Во втором методе происходит чистой воды волшебство. Мы создаём экземпляр класса UITableViewCell(cell), используя стиль ячейки Subtitle. Затем мы присваиваем текстовому значению этой ячейки значение "Row #\(indexPath.row)". В Swift подобным образом происходит встраивание значений переменных в строку без конвертации (таким образом, мы получим строки вида "Row #1", "Row #2")

Детализированная текстовая метка (detailTextLabel) доступна только при использовании класса ячеек Subtitle, который мы используем в данном примере.

При запуске приложения вы увидите восхитительный список ячеек с заголовками и подзаголовками, показывающие номера их строк. Это один из наиболее распространённых способов отображения данных в iOS, вы убедитесь, он вам ещё не раз пригодится. Увидеть полный текст кода вы можете на github.

В следующем посте мы исследуем использование API поиска iTunes для создания приложения, способного искать и отображать альбомы внутри iTunes Store.

Вопрос о дальнейшем переводе Уважаемые Хабралюди! Если вы прочли этот перевод, помогите автору определиться с будущим цикла. Для этого всего лишь требуется поучаствовать в опросе.

m.habr.com


scroll to top