<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Just Developer Blog, заметки с тегом: iOS development</title>
<link>http://bibobo.ru/tags/ios-development/</link>
<description></description>
<generator>E2 (v3254; Aegea)</generator>

<item>
<title>Работа с AVAudioPlayer и MPNowPlayingInfo (iOS)</title>
<guid isPermaLink="true">http://bibobo.ru/all/rabota-s-avaudioplayer-i-mpnowplayinginfo-ios/</guid>
<link>http://bibobo.ru/all/rabota-s-avaudioplayer-i-mpnowplayinginfo-ios/</link>
<comments>http://bibobo.ru/all/rabota-s-avaudioplayer-i-mpnowplayinginfo-ios/</comments>
<description>&lt;p&gt;В ходе разработки приложений под iOS часто возникает необходимость проиграть аудио-файл, например композицию или эффект. Наиболее простым способом для этого в iOS является использование класса AVAudioPlayer. Он является частью фреймворка AVFoundation, так что нам потребуется поставить на него using.&lt;/p&gt;
&lt;p&gt;Ниже представлен код хэлпера, упрощающего работу с этим классом.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_ios_avaudioplayer" type="button" onclick="toggle(cut_ios_avaudioplayer, button_ios_avaudioplayer)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_ios_avaudioplayer" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Хэлпер, упрощающий работу с плейером (в системе всегда висит один статический плейер)
/// &amp;lt;/summary&amp;gt;
public static class PlayerHelper
{
    /// &amp;lt;summary&amp;gt;
    /// Состояние плейера
    /// &amp;lt;/summary&amp;gt;
    public enum PlayerState
    {
        Uncknown,
        Playing,
        Stopped,
        Paused
    }

    /// &amp;lt;summary&amp;gt;
    /// Состояние плейера
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;value&amp;gt;The state.&amp;lt;/value&amp;gt;
    public static PlayerState State { get; private set; }

    private static AVAudioPlayer _player;
    public static AVAudioPlayer Player { get{ return _player; } }

    /// &amp;lt;summary&amp;gt;
    /// Включена или нет функция зацикливания
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;value&amp;gt;&amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt; if is infinite playing; otherwise, &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt;.&amp;lt;/value&amp;gt;
    public static bool IsInfinitePlaying
    {
        get{ return _player != null ? _player.NumberOfLoops == int.MaxValue : false; }
        set
        {
            if (_player == null)
            {
                return;
            }

            _player.NumberOfLoops = value ? int.MaxValue : 0;
        }
    }

    static PlayerHelper()
    {
        State = PlayerState.Uncknown;
    }

    /// &amp;lt;summary&amp;gt;
    /// Проиграть URL с диска (если он уже проигрывается, воспроизведение продолжится с места остановки)
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;The URL.&amp;lt;/returns&amp;gt;
    /// &amp;lt;param name=&amp;quot;url&amp;quot;&amp;gt;URL.&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;finishedPlayingAction&amp;quot;&amp;gt;Finished playing action.&amp;lt;/param&amp;gt;
    public static AVAudioPlayer PlayUrl(NSUrl url, Action finishedPlayingAction = null)
    {
        //в тех случаях, когда плейер уже проигрывает переданный url - не нужно останавливать запись или начинать сначала
        if (IsPlayerLoadedThisFile(url))
        {
            ResumePlaying();
            return _player;
        }

        StopPlaying();

        _player = AVAudioPlayer.FromUrl(url);
        _player.CurrentTime = 0;
        _player.Volume = 1f;

        if (finishedPlayingAction != null)
        {
            _player.FinishedPlaying += (sender, e) =&amp;gt;
            {
                finishedPlayingAction.Invoke();
            };
        }

        _player.PrepareToPlay();

        //разрешаем проигрывание в бекграунде
        AVAudioSession.SharedInstance().SetCategory(AVAudioSessionCategory.Playback);
        AVAudioSession.SharedInstance().SetActive(true);
        UIApplication.SharedApplication.BeginReceivingRemoteControlEvents();

        _player.Play();

        State = PlayerState.Playing;

        return _player;
    }

    /// &amp;lt;summary&amp;gt;
    /// Проиграть
    /// &amp;lt;/summary&amp;gt;
    public static void Play()
    {
        if (_player == null)
        {
            return;
        }

        _player.Play();
        State = PlayerState.Playing;
    }

    /// &amp;lt;summary&amp;gt;
    /// Остановить
    /// &amp;lt;/summary&amp;gt;
    public static void StopPlaying()
    {
        if (_player == null)
        {
            return;
        }

        _player.Stop();
        State = PlayerState.Stopped;
    }

    /// &amp;lt;summary&amp;gt;
    /// Приостановить
    /// &amp;lt;/summary&amp;gt;
    public static void PausePlaying()
    {
        if (_player == null)
        {
            return;
        }

        _player.Pause();
        State = PlayerState.Paused;
    }

    /// &amp;lt;summary&amp;gt;
    /// Продолжить
    /// &amp;lt;/summary&amp;gt;
    public static void ResumePlaying()
    {
        if (_player == null)
        {
            return;
        }

        _player.Play();
        State = PlayerState.Playing;
    }

    /// &amp;lt;summary&amp;gt;
    /// Возвратит true, если плейер загрузил файл по этому URL с диска
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt; if is player loaded this file the specified url; otherwise, &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt;.&amp;lt;/returns&amp;gt;
    /// &amp;lt;param name=&amp;quot;url&amp;quot;&amp;gt;URL.&amp;lt;/param&amp;gt;
    public static bool IsPlayerLoadedThisFile(NSUrl url)
    {
        if (_player == null)
        {
            return false;
        }

        return _player.Url.LastPathComponent == url.LastPathComponent;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Так же при проигрывании композиции (если в info.plist прописано разрешение на проигрывание аудио в бекграунде) на главном экране отображается информация о композиции, а так же кнопки управления воспроизведением. Чтобы информация отображалась корректно необходимо при старте проигрывания трека написать следующий код:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;MPNowPlayingInfo info = new MPNowPlayingInfo();
info.Title = &amp;quot;Имя композиции&amp;quot;;
info.Artist = &amp;quot;наименование артиста&amp;quot;;

info.PlaybackRate = 1f;
info.PlaybackDuration = PlayerHelper.Player.Duration;
info.ElapsedPlaybackTime = PlayerHelper.Player.CurrentTime;

MPNowPlayingInfoCenter.DefaultCenter.NowPlaying = info;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
<pubDate>Mon, 18 Jan 2016 23:11:20 +0300</pubDate>
</item>

<item>
<title>Использование QLPreviewController для просмотра файлов (iOS)</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-qlpreviewcontroller-dlya-prosmotra-faylov-ios/</guid>
<link>http://bibobo.ru/all/ispolzovanie-qlpreviewcontroller-dlya-prosmotra-faylov-ios/</link>
<comments>http://bibobo.ru/all/ispolzovanie-qlpreviewcontroller-dlya-prosmotra-faylov-ios/</comments>
<description>&lt;p&gt;Для реализации просмотра файлов различных форматов (изображения, word, excel, txt, медиа и т. п.) в iOS существует очень удобный класс QLPreviewController. Для его удобного использования необходимо сделать следующее:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Создать класс-наследник QLPreviewController, который инкапсулирует все детали реализации&lt;/li&gt;
&lt;li&gt;Создать класс QLPreviewControllerDataSource, который будет являться источником данных для контроллера&lt;/li&gt;
&lt;li&gt;Создать классы-наследники QLPreviewItem, которые будут хранить информацию о просматриваемом элементе&lt;br /&gt;
&lt;input id="button_ios_qlpreview" type="button" onclick="toggle(cut_ios_qlpreview, button_ios_qlpreview)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div id="cut_ios_qlpreview" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Без лишних слов сразу взглянем на код контроллера&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Контроллер, который позволяет отобразить документы (изображения, ворд, экзель, пдф и т.п.) в виде превью
/// поддерживает несколько документов для отображения
/// &amp;lt;/summary&amp;gt;
public class QuickLookPreviewController : QLPreviewController
{
    /// &amp;lt;summary&amp;gt;
    /// Отобразить список NSUrl'ов
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt;, if urls was previewed, &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt; otherwise.&amp;lt;/returns&amp;gt;
    /// &amp;lt;param name=&amp;quot;urls&amp;quot;&amp;gt;Urls.&amp;lt;/param&amp;gt;
    public bool PreviewUrls(params NSUrl[] urls)
    {
        List&amp;lt;QLPreviewItem&amp;gt; items = new List&amp;lt;QLPreviewItem&amp;gt;();
        foreach (NSUrl url in urls)
        {
            QLPreviewItemNsUrl item = new QLPreviewItemNsUrl(url);
            if (QLPreviewController.CanPreviewItem(item))
            {
                items.Add(item);
            }
        }

        return PreviewItems(items.ToArray());
    }

    /// &amp;lt;summary&amp;gt;
    /// Отобразить список QLPreviewItem
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;&amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt;, if items was previewed, &amp;lt;c&amp;gt;false&amp;lt;/c&amp;gt; otherwise.&amp;lt;/returns&amp;gt;
    /// &amp;lt;param name=&amp;quot;items&amp;quot;&amp;gt;Items.&amp;lt;/param&amp;gt;
    public bool PreviewItems(params QLPreviewItem[] items)
    {
        if (items.Length &amp;gt; 0)
        {
            DataSource = new PreviewControllerDataSource(items);
            return true;
        }

        return false;
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview источник данных
    /// &amp;lt;/summary&amp;gt;
    private class PreviewControllerDataSource : QLPreviewControllerDataSource
    {
        private QLPreviewItem[] _items = new QLPreviewItem[0];

        public PreviewControllerDataSource(params QLPreviewItem[] items)
        {
            _items = items;
        }

        public override nint PreviewItemCount(QLPreviewController controller)
        {
            return _items.Length;
        }

        public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
        {
            return _items[index];
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview Item, принимающий NSUrl
    /// &amp;lt;/summary&amp;gt;
    public class QLPreviewItemNsUrl : QLPreviewItem
    {
        private NSUrl _documentUrl;

        public QLPreviewItemNsUrl(NSUrl documentUrl)
        {
            _documentUrl = documentUrl;
        }

        public override string ItemTitle
        {
            get
            {
                return _documentUrl.LastPathComponent;
            }
        }
        public override NSUrl ItemUrl
        {
            get
            {
                return _documentUrl;
            }
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview Item, принимающий имя и путь к файлу из файловой системы
    /// &amp;lt;/summary&amp;gt;
    public class QLPreviewItemFileSystem : QLPreviewItem
    {
        string _fileName, _filePath;

        public QLPreviewItemFileSystem(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }
        public override NSUrl ItemUrl
        {
            get
            {
                return NSUrl.FromFilename(_filePath);
            }
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// QL Preview Item, принимающий имя ипуть к файлу из бандла
    /// &amp;lt;/summary&amp;gt;
    public class QLPreviewItemBundle : QLPreviewItem
    {
        string _fileName, _filePath;
        public QLPreviewItemBundle(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }
        public override NSUrl ItemUrl
        {
            get
            {
                var documents = NSBundle.MainBundle.BundlePath;
                var lib = Path.Combine(documents, _filePath);
                var url = NSUrl.FromFilename(lib);
                return url;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Использовать данный код можно следующим образом:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;QuickLookPreviewController previewController = new QuickLookPreviewController();
if (previewController.PreviewUrls(new NSUrl[]{ fileUrl }))
{
    this.NavigationController.PushViewController(previewController, true);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
<pubDate>Wed, 13 Jan 2016 22:10:14 +0300</pubDate>
</item>

<item>
<title>Создание и удаление записей в календаре (iOS)</title>
<guid isPermaLink="true">http://bibobo.ru/all/sozdanie-i-udalenie-zapisey-v-kalendare-ios/</guid>
<link>http://bibobo.ru/all/sozdanie-i-udalenie-zapisey-v-kalendare-ios/</link>
<comments>http://bibobo.ru/all/sozdanie-i-udalenie-zapisey-v-kalendare-ios/</comments>
<description>&lt;p&gt;Многие приложения используют очень полезную фишку ОС от Apple как создание кастомных записей и календарей. Сегодня мы рассмотрим пример реализации класса-хэлпера, позволяющего создавать и удалять записи в кастомном календаре.&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Вся работа с календарем строится через фреймворк EventKit, при этом последовательность действий для создания записи примерно следующая:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Создать экземпляра класса EKEventStore&lt;/li&gt;
&lt;li&gt;Запросить через него разрешение на работу с календарем&lt;/li&gt;
&lt;li&gt;В completionHandler реализовать код, который будет создавать запись&lt;/li&gt;
&lt;li&gt;При создании записи необходимо сначала найти наш кастомный календарь EKCalendar (или создать его, если его еще нет)&lt;/li&gt;
&lt;li&gt;Создать экземпляр класса EKEvent (это и будет наша запись) и установить для него заголовок, сопроводительный текст, время начала и окончания&lt;/li&gt;
&lt;li&gt;Создать и добавить ремайндер (экземпляр класса EKAlarm), который описывает когда должно сработать напоминание о записи&lt;br /&gt;
&lt;input id="button_ios_manage_calendar_events" type="button" onclick="toggle(cut_ios_manage_calendar_events, button_ios_manage_calendar_events)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div id="cut_ios_manage_calendar_events" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Более подробно можно понять, посмотрев код:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;public class EventKitCalendarHelper
{
    /// &amp;lt;summary&amp;gt;
    /// Имя кастомного календаря
    /// &amp;lt;/summary&amp;gt;
    public static string CalendarTitle = &amp;quot;CalendarTitle&amp;quot;;

    /// &amp;lt;summary&amp;gt;
    /// Варианты срабатывания напоминания для записи в календаре
    /// &amp;lt;/summary&amp;gt;
    public enum AlarmTime
    {
        None = 0,
        FifteenMinutesBefore = -15*60,
        HalfAnHourBefore = -30*60,
        AnHourBefore = -60*60,
        TwoHoursBefore = -2*60*60,
        ThreeHoursBefore = -3*60*60
    }

    /// &amp;lt;summary&amp;gt;
    /// Добавляет запись в календаре
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&amp;quot;eventTitle&amp;quot;&amp;gt;Event title.&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;dateStart&amp;quot;&amp;gt;Date start.&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;timeLength&amp;quot;&amp;gt;Time length.&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;time&amp;quot;&amp;gt;Time.&amp;lt;/param&amp;gt;
    public static void AddCalendarEvent(string eventTitle, DateTime dateStart, TimeSpan timeLength, string eventNotes = &amp;quot;&amp;quot;, AlarmTime time = AlarmTime.None)
    {
        if (string.IsNullOrEmpty(eventTitle))
        {
            return;
        }

        EKEventStore store = new EKEventStore();
        store.RequestAccess(EKEntityType.Event, (granted, err) =&amp;gt;
            {
                if (!granted)
                {
                    return;
                }

                EKEvent entry = EKEvent.FromStore(store);
                entry.Title = eventTitle;
                entry.Notes = eventNotes;
                entry.StartDate = dateStart.DateTimeToNSDate();
                entry.EndDate = dateStart.Add(timeLength).DateTimeToNSDate();

                //ищем наш кастомный календарь, если мы его не находим - создаем новый
                EKCalendar calendar = FindOrCreateCalendar(store, true);
                if(calendar == null)
                {
                    return;
                }

                entry.Calendar = calendar;

                //создаем ремайндер, если он требуется
                if (time != AlarmTime.None)
                {
                    entry.AddAlarm(EKAlarm.FromTimeInterval((int)time));
                }

                err = null;
                store.SaveEvent(entry, EKSpan.ThisEvent, out err);
            });
    }

    /// &amp;lt;summary&amp;gt;
    /// Находит или создает кастомный календарь
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;The or create calendar.&amp;lt;/returns&amp;gt;
    /// &amp;lt;param name=&amp;quot;store&amp;quot;&amp;gt;Store.&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;createIfDoesntExist&amp;quot;&amp;gt;If set to &amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt; create if doesnt exist.&amp;lt;/param&amp;gt;
    private static EKCalendar FindOrCreateCalendar(EKEventStore store, bool createIfDoesntExist = false)
    {
        NSError err = null;

        EKCalendar calendar = store.Calendars.Where(x =&amp;gt; x.Title == CalendarTitle).FirstOrDefault();
        if (calendar == null &amp;amp;&amp;amp; createIfDoesntExist)
        {
            err = null;
            calendar = EKCalendar.Create(EKEntityType.Event, store);
            calendar.Title = CalendarTitle;

            EKSource localSource = store.Sources.Where(x =&amp;gt; x.SourceType == EKSourceType.Local).FirstOrDefault();
            calendar.Source = localSource;

            store.SaveCalendar(calendar, true, out err);
        }

        return calendar;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;С удалением событий дела обстоят интереснее. При создании события у него есть строковое поле EventIdentifier, его можно где-то сохранить и использовать позже для удаления записи. Либо можно найти все записи при помощи специальным образом сформированного предиката и удалить их. Рассмотрим второй вариант.&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Удаляет записи в календаре, которые соответствуют выбранному интервалу времени
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;dateStart&amp;quot;&amp;gt;Date start.&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;timeLength&amp;quot;&amp;gt;Time length.&amp;lt;/param&amp;gt;
public static void RemoveCalendarEvent(DateTime dateStart, TimeSpan timeLength, string title = &amp;quot;&amp;quot;)
{
    EKEventStore store = new EKEventStore();
    store.RequestAccess(EKEntityType.Event, (granted, err) =&amp;gt;
        {
            if (!granted)
            {
                return;
            }

            //ищем наш кастомный календарь
            EKCalendar calendar = FindOrCreateCalendar(store);
            if (calendar == null)
            {
                return;
            }

            //создаем предикат для поиска эвентов в нашем календаре
            NSPredicate predicate = store.PredicateForEvents(dateStart.DateTimeToNSDate(), dateStart.Add(timeLength).DateTimeToNSDate(), new EKCalendar[]{ calendar });
            EKEvent[] entries = store.EventsMatching(predicate);

            if (entries == null || entries.Length == 0)
            {
                return;
            }

            //если нужно - профильтруем элементы календаря по заголовку
            if (!string.IsNullOrEmpty(title))
            {
                entries = entries.Where(x =&amp;gt; x.Title == title).ToArray();
            }

            if (entries == null || entries.Length == 0)
            {
                return;
            }

            //проходимся по всем найденным эвентам и удаляем их
            foreach (EKEvent entry in entries)
            {
                err = null;
                store.RemoveEvent(entry, EKSpan.ThisEvent, true, out err);
            }
        });
}&lt;/code&gt;&lt;/pre&gt;</description>
<pubDate>Wed, 06 Jan 2016 18:38:03 +0300</pubDate>
</item>

<item>
<title>Использование NSUrlSession для загрузки файлов (iOS)</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-nsurlsession-dlya-zagruzki-faylov-ios/</guid>
<link>http://bibobo.ru/all/ispolzovanie-nsurlsession-dlya-zagruzki-faylov-ios/</link>
<comments>http://bibobo.ru/all/ispolzovanie-nsurlsession-dlya-zagruzki-faylov-ios/</comments>
<description>&lt;p&gt;При разработке мобильного приложения очень часто возникает необходимость организовать загрузку файла по сети. При этом если разработка ведется на Xamarin высоко желание использовать стандартный WebClient для этих целей. Все бы ничего, но при использовании WebClient возникает несколько сложностей, например:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Не высокая производительность&lt;/li&gt;
&lt;li&gt;Прерывание загрузки при блокировке устройства или уходе приложения в бекграунд&lt;br /&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Для решения вопроса с загрузкой файлов наиболее правильным способом будет кардинально переписать загрузку на использование NSUrlSession.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_ios_download_file_in_background" type="button" onclick="toggle(cut_ios_download_file_in_background, button_ios_download_file_in_background)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_ios_download_file_in_background" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;NSUrlSession — это системный механизм iOS, позволяющий организовать длительные процессы загрузки и заливки данных по сети, при этом система сама правильно обработает обрывы связи, докачает файл, если это возможно и уведомит приложение о результате.&lt;/p&gt;
&lt;p&gt;Чтобы использовать NSUrlSession нам потребуется сделать следующее:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Создать класс-хэлпер, который будет инкапсулировать всю логику работы с NSUrlSession&lt;/li&gt;
&lt;li&gt;Создать класс-делегат наследник NSUrlSessionDownloadDelegate, который будет уведомлять внешний код о прогрессе загрузки файла, а так же об окончании загрузки&lt;/li&gt;
&lt;li&gt;Создать экземпляр класса NSUrlSession и присвоить ему делегат&lt;/li&gt;
&lt;li&gt;Для каждого URL файла создать NSUrlRequest и NSUrlSessionDownloadTask, и запустить скачивание&lt;/li&gt;
&lt;li&gt;Так же необходимо позаботиться о том, чтобы не потерять информацию о том, какому Url соответствует какой NSUrlSessionDownloadTask, чтобы потом корректно сообщать внешнему коду о ходе скачивания&lt;br /&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Далее код готового решения с комментариями о деталях реализации:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Класс, который реализует всю работу с механизмом NSUrlSession для реализации загрузки файлов в фоновом режиме
/// &amp;lt;/summary&amp;gt;
public class DownloadSession
{
    /// &amp;lt;summary&amp;gt;
    /// Создаем экземпляр конфигурации для сессии (с таким ID сессия может быть только одна в приложении)
    /// &amp;lt;/summary&amp;gt;
    private static NSUrlSessionConfiguration _downloadConfiguration = NSUrlSessionConfiguration.BackgroundSessionConfiguration(&amp;quot;com.appid.background.download&amp;quot;);

    /// &amp;lt;summary&amp;gt;
    /// Класс-делегат, инкапсулирует всю логику по уведомлению внешнего кода о состоянии загрузки, а так же занимается менеджментом хранения ссылок на скачиваемые файлы
    /// &amp;lt;/summary&amp;gt;
    public sealed class ChatBackgroundSessionDelegate : NSUrlSessionDownloadDelegate
    {
        /// &amp;lt;summary&amp;gt;
        /// Словарь, который хранит соответствие - таск загрузки &amp;lt;--&amp;gt; url файла
        /// &amp;lt;/summary&amp;gt;
        public Dictionary&amp;lt;NSUrlSessionDownloadTask, string&amp;gt; QueueHolder { get; private set; }

        public delegate void DownloadingCompleteDelegate(string url, NSUrl tempFileLocation);
        public delegate void DownloadProgressDelegate(string url, int progressPercent);

        /// &amp;lt;summary&amp;gt;
        /// Экшн, который будет вызываться при окончании загрузки файла (в параметрах будут url файла и NSUrl на временный скачанный файл)
        /// &amp;lt;/summary&amp;gt;
        public DownloadingCompleteDelegate CompleteAction { get{ return _completeAction; } set{ _completeAction = value; } }
        private DownloadingCompleteDelegate _completeAction;

        /// &amp;lt;summary&amp;gt;
        /// Экшн, который будет вызываться при изменении прогресса скачивания файла (в параметрах будет url файла и процент скачивания)
        /// &amp;lt;/summary&amp;gt;
        public DownloadProgressDelegate ProgressDelegate { get{ return _progressDelegate; } set{ _progressDelegate = value; } }
        private DownloadProgressDelegate _progressDelegate;

        /// &amp;lt;summary&amp;gt;
        /// Конструктор, который создает словарь-очередь и присваивает все необходимые экшны
        /// &amp;lt;/summary&amp;gt;
        public ChatBackgroundSessionDelegate(DownloadingCompleteDelegate downloadCompleteAction, DownloadProgressDelegate progressDelegate = null)
        {
            _completeAction = downloadCompleteAction;
            _progressDelegate = progressDelegate;

            QueueHolder = new Dictionary&amp;lt;NSUrlSessionDownloadTask, string&amp;gt;();
        }

        /// &amp;lt;summary&amp;gt;
        /// Метод, вызывается системой при изменении прогресса скачивания файлов
        /// &amp;lt;/summary&amp;gt;
        public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
        {
            if (_progressDelegate == null || !QueueHolder.ContainsKey(downloadTask))
            {
                return;
            }

            //если в очереди найден такой файл и экшн был передан извне - то вызываем экшн в основном потоке и вычисляем процент скачивания файла
            InvokeOnMainThread(() =&amp;gt;
                {
                    _progressDelegate.Invoke(QueueHolder[downloadTask], Convert.ToInt32(totalBytesWritten * 100 / totalBytesExpectedToWrite));
                });
        }

        /// &amp;lt;summary&amp;gt;
        /// Метод, который вызывается при завершении загрузки файла
        /// &amp;lt;/summary&amp;gt;
        public override void DidFinishDownloading(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
        {
            if (_completeAction == null || !QueueHolder.ContainsKey(downloadTask))
            {
                return;
            }

            InvokeOnMainThread(() =&amp;gt;
                {
                    _completeAction.Invoke(QueueHolder[downloadTask], location);
                });
        }
    }

    private ChatBackgroundSessionDelegate _delegate;
    private NSUrlSession _downloadSession;

    /// &amp;lt;summary&amp;gt;
    /// Конструктор класса-холдера
    /// &amp;lt;/summary&amp;gt;
    public DownloadSession(ChatBackgroundSessionDelegate.DownloadingCompleteDelegate downloadCompleteAction,
        ChatBackgroundSessionDelegate.DownloadProgressDelegate progressDelegate = null)
    {
        //создаем делегат
        _delegate = new ChatBackgroundSessionDelegate(downloadCompleteAction, progressDelegate);

        //создаем сессию (при этом если сессия с такой конфигурацией уже есть в системе - то новая сессия создана не будет, а в переменную попадет старая сессия)
        _downloadSession = NSUrlSession.FromConfiguration(
            _downloadConfiguration,
            _delegate,
            new NSOperationQueue());
    }

    /// &amp;lt;summary&amp;gt;
    /// Стартует процесс загрузки
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&amp;quot;url&amp;quot;&amp;gt;URL.&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;file&amp;quot;&amp;gt;File.&amp;lt;/param&amp;gt;
    /// &amp;lt;param name=&amp;quot;message&amp;quot;&amp;gt;Message.&amp;lt;/param&amp;gt;
    public void StartDownload(string url)
    {
        if (string.IsNullOrEmpty(url))
        {
            return;
        }

        NSUrl nsurl = NSUrl.FromString(new NSString(url).CreateStringByAddingPercentEscapes(NSStringEncoding.UTF8));
        NSUrlRequest request = NSUrlRequest.FromUrl(nsurl);
        NSUrlSessionDownloadTask task = _downloadSession.CreateDownloadTask(request);

        //нужно именно попробовать создать сессию и только после этого взяь ее делегат, кастануть его к нужному типу и забиндить реквесты
        //делаем так, потому что сессия глобально в системе создается только одна и при попытке повторного ее создания - новый делегат просто не присваивается :)
        //экшны копируются из нового делегата в старый, потому что мы никак физически не можем заменить делегат (он только для чтения)
        if (_delegate != (ChatBackgroundSessionDelegate)_downloadSession.Delegate)
        {
            ChatBackgroundSessionDelegate newDelegate = (ChatBackgroundSessionDelegate)_downloadSession.Delegate;
            newDelegate.CompleteAction = _delegate.CompleteAction;
            newDelegate.ProgressDelegate = _delegate.ProgressDelegate;
        }
        _delegate = (ChatBackgroundSessionDelegate)_downloadSession.Delegate;

        //добавляем наш Url и таск в очередь
        _delegate.QueueHolder.Add(task, url);

        //стартуем загрузку
        task.Resume();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Использовать все это хозяйство можно следующим образом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;//создаем сессию загрузки данных
DownloadSession session = new DownloadSession((url, tempFileLocation) =&amp;gt;
    {
        //если завершена загрузка файла - сохраняем его на диск
        if (item.url == url)
        {
            /////////////////
        }
    }, (url, percent) =&amp;gt;
    {
        if (item.url == url)
        {
            //отображаем прогресс загрузки
        }
    });

//стартуем загрузку
session.StartDownload(item.url);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
<pubDate>Sun, 03 Jan 2016 10:53:01 +0300</pubDate>
</item>

<item>
<title>Применение констрейнтов для загруженного Xib в iOS</title>
<guid isPermaLink="true">http://bibobo.ru/all/primenenie-konstreyntov-dlya-zagruzhennogo-xib-v-ios/</guid>
<link>http://bibobo.ru/all/primenenie-konstreyntov-dlya-zagruzhennogo-xib-v-ios/</link>
<comments>http://bibobo.ru/all/primenenie-konstreyntov-dlya-zagruzhennogo-xib-v-ios/</comments>
<description>&lt;p&gt;Очень часто возникает ситуация, когда необходимо переиспользовать часть интерфейса. Для решения этого вопроса в iOS лучше всего подходит вынесение элемента интерфейса в отдельный Xib-файл и последующая его загрузка из кода. Когда мы грузим Xib как ячейку таблицы — он скаллируется автоматически в зависимости от размера ячейки, но если загружать и добавлять его например в произвольный контроллер или View — то могут возникнуть сложности с масштабированием View из Xib при ресайзе родительской View.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Начиная с iOS 7 Apple дает нам возможность решать вопрос масштабирования при помощи нового инструмента — Auto Layout Constraints. Но при загрузке View из кода ей не проставляются никакие констрейнты автоматически.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Выход — добавить констрейнты вручную.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_ios_xib_constraints" type="button" onclick="toggle(cut_ios_xib_constraints, button_ios_xib_constraints)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_ios_xib_constraints" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Для этого после загрузки View из Xib напишем следующий код:&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;//запрещаем трансляцию autoresizing mask в констрейнты (поскольку в xib-е она включена по умолчанию)
viewFromXib.TranslatesAutoresizingMaskIntoConstraints = false;

viewController.View.Add(viewFromXib);

//добавляем констрейнты для авторесайза вьюхи из xib-а после того, как мы эту вьюху добавили в иерархию родителя
NSDictionary viewDict = NSDictionary.FromObjectAndKey(viewFromXib, new NSString(&amp;quot;viewFromXib&amp;quot;));
viewController.View.AddConstraints(NSLayoutConstraint.FromVisualFormat(new NSString(&amp;quot;H:|[viewFromXib]|&amp;quot;), 0, null, viewDict));
viewController.View.AddConstraints(NSLayoutConstraint.FromVisualFormat(new NSString(&amp;quot;V:|[viewFromXib]|&amp;quot;), 0, null, viewDict));&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
<pubDate>Tue, 24 Nov 2015 20:44:18 +0300</pubDate>
</item>

<item>
<title>Динамическое изменение лэйаута для прортретной и ландшафтной ориентации на iPad</title>
<guid isPermaLink="true">http://bibobo.ru/all/dinamicheskoe-izmenenie-leyauta-dlya-prortretnoy-i-landshaftnoy/</guid>
<link>http://bibobo.ru/all/dinamicheskoe-izmenenie-leyauta-dlya-prortretnoy-i-landshaftnoy/</link>
<comments>http://bibobo.ru/all/dinamicheskoe-izmenenie-leyauta-dlya-prortretnoy-i-landshaftnoy/</comments>
<description>&lt;p&gt;Хороший интерфейс пользователя должен быть адаптивным и изменяться в зависимости от размера и соотношения сторон экрана. Всем известно, что компания Apple обладает своим собственным подходом к построению интерфейса. В отличии от Android, для того чтобы нарисовать интерфейс для iOS не достаточно описать его с помощью декларативного языка. На iOS придется работать в Interface Builder, рисовать контролы ручками и описывать их взаимоотношения при помощи “ограничений” (Auto Layout Constraints).&lt;/p&gt;
&lt;p&gt;&lt;input id="button_ios_ipad_layout" type="button" onclick="toggle(cut_ios_ipad_layout, button_ios_ipad_layout)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_ios_ipad_layout" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Точно так же Apple обладает своим собственным видением того, как реагировать на изменение размера и соотношений сторон экрана. Для того, чтобы правильно отображать интерфейс для различных экранов Apple предлагает разработчику инструмент под названием Size Classes. Если кратко — то всего существует 2 сайз класса (один для горизонтали, второй для вертикали), каждый из которых может иметь одно из двух значений (Regular — обычный и Compact — компактный). При этом в зависимости от типа устройства и его ориентации эти сайз классы изменяют свои значения.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Например для iPhone 5 в портретной ориентации горизонтальный сайз класс будет иметь значение Compact, а вертикальный — Regular. Если iPhone развернуть в ландшафт — то сайз классы поменяются местами (горизонталь станет Regular, вертикаль — Compact).&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Таким образом начиная с iOS 7 на Size classes завязано все изменение лэйаутов в Interface Builder. При этом нельзя нарисовать два лэйаута для разных наборов сайз классов — нам необходимо включать или выключать констрейнты (Auto Layout Constraints) в зависимости от значения сайз класса. Уже на устройстве система сама поймет какие сайз классы в данный момент активны и автоматически отключит или включит соответствующие констрейнты, интерфейс будет выглядеть по разному в ландшафте или портрете.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;В случае iPad картина выглядит иначе. &lt;b&gt;Планшет обладает одинаковым значением сайз классов для горизонтали и вертикали в любой ориентации.&lt;/b&gt; Такое поведение системы добавляет некоторые сложности, когда нам нужно для портрета и ландшафта иметь различные лэйауты, например так:&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="570" data-ratio="0.96446700507614"&gt;
&lt;img src="http://bibobo.ru/pictures/1_1.png" width="570" height="591" alt="" /&gt;
&lt;img src="http://bibobo.ru/pictures/2_1.png" width="766" height="393" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Но существует способ обойти такое поведение системы, для этого необходимо выполнить следующее:&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Добавить отдельные констрейнты для ландшафта и портрета&lt;/li&gt;
&lt;li&gt;Вывести две &lt;i&gt;IBOutlet Collection&lt;/i&gt; (одну для портрета, вторую для ландшафта) и привязать соответствующие констрейнты к соответствующему аутлету&lt;/li&gt;
&lt;li&gt;В коде подписаться обзервером на событие &lt;i&gt;UIDeviceOrientationDidChangeNotification&lt;/i&gt;. По событию проверять идиому интерфейса пользователя и по ориентации статус бара понимать как развернуто устройство, и активировать соответствующий набор констрейнтов&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Пройдемся краткой по основным этапам.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;Добавить отдельные констрейнты для ландшафта и портрета&lt;/h2&gt;
&lt;p&gt;Чтобы правильно добавить необходимые констрейнты необходимо действовать в следующем порядке:&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;1. Для начала нарисовать лэйаут под портретную ориентацию&lt;br/&gt;
2. Понять какие констрейнты будут общими, а какие будут относиться только к портрету&lt;br/&gt;
3. Для каждого &lt;b&gt;не общего&lt;/b&gt; констрейнта добавить его вариант для любой ширины и Regular высоты (Any Width &amp; Regular Height)&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/3_1.png" width="401" height="343" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;4. Далее выключить этот констрейнт в версии без указания сайз классов&lt;br/&gt;
5. Повторить пункты 3 и 4 для всех констрейнтов, которые будут уникальными для портретной ориентации&lt;br/&gt;
6. Теперь необходимо на том же экране добавить констрейнты для ландшафтной ориентации&lt;br/&gt;
7. Для всех констрейнтов, уникальных для ландшафтной ориентации добавить их варианты для любой ширины и компактной высоты (Any Width &amp; Compact Height)&lt;br/&gt;
8. Далее выключить этот констрейнт в версии без указания сайз классов&lt;br/&gt;
9. Повторить пункты 7 и 8 для всех констрейнтов, которые будут уникальными для ландшафтной ориентации
&lt;/p&gt;
&lt;h2&gt;Вывести две IBOutlet Collection (одну для портрета, вторую для ландшафта) и привязать соответствующие констрейнты к соответствующему аутлету&lt;/h2&gt;
&lt;p&gt;Например начнем с портретных констрейнтов, откроем Assistant Editor, найдем в левом пане наш констрейнт (он сейчас должен быть выключен) и перетянем его (&lt;b&gt;с зажатым Ctrl&lt;/b&gt;) в пан с кодом (Assistant Editor)&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/4_1.png" width="1253" height="485" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;В окне задания свойств аутлета необходимо ввести его имя и обязательно выбрать в поле Connection значение &lt;b&gt;Outlet Collection&lt;/b&gt;.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/5_1.png" width="337" height="141" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Далее нажать Connect — наш новый аутлет свяжется с констрейнтом.
Далее необходимо осуществить хитрость — не достаточно просто повторить предыдущие шаги с оставшимися констрейнтами для портретной ориентации. Оставшиеся коснтрейнты необходимо соединять с нашим аутлетом через окно инспектора соединений (Connections Inspector). Для этого необходимо выделить констрейнт, открыть его инспектор соединений и из поля Referencing Outlet Collections вытянуть соединение с нашим аутлетом.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://bibobo.ru/pictures/6.png" width="620" height="471" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;На этом работа в Interface Builder закончена.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;h2&gt;В коде подписаться обзервером на событие “UIDeviceOrientationDidChangeNotification”&lt;/h2&gt;
&lt;p&gt;В коде нашего контроллера в методе &lt;i&gt;viewDidLoad&lt;/i&gt; необходимо подписаться на событие обзервера &lt;i&gt;UIDeviceOrientationDidChangeNotification&lt;/i&gt;. Оно будет вызываться всякий раз, когда ориентация устройства изменяется.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;NSNotificationCenter.DefaultCenter.AddObserver(new NSString(&amp;quot;UIDeviceOrientationDidChangeNotification&amp;quot;), (notification) =&amp;gt;
{
	UpdateControlsFrames();
});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Далее необходимо реализовать метод &lt;i&gt;UpdateControlsFrames()&lt;/i&gt; и внутри него по идиоме интерфейса и положению статус бара активировать констрейнты либо портрета, либо ландшафта.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;void UpdateControlsFrames()
{
	if ((new UIDevice()).UserInterfaceIdiom != UIUserInterfaceIdiom.Pad)
	{
		return;
	}

	bool isPortraint = true;
	if (UIApplication.SharedApplication.StatusBarOrientation == UIInterfaceOrientation.LandscapeLeft
		|| UIApplication.SharedApplication.StatusBarOrientation == UIInterfaceOrientation.LandscapeRight)
	{
		isPortraint = false;
	}

	if (isPortraint)
	{
		NSLayoutConstraint.DeactivateConstraints(landscapeLayoutConstraints);
		NSLayoutConstraint.ActivateConstraints(portraintLayoutConstraints);
	}
	else
	{
		NSLayoutConstraint.DeactivateConstraints(portraintLayoutConstraints);
		NSLayoutConstraint.ActivateConstraints(landscapeLayoutConstraints);
	}

	View.SetNeedsLayout();
	View.LayoutSubviews();
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Стоит сказать о том, что после активации констрейнтов необходимо вызвать &lt;i&gt;SetNeedsLayout()&lt;/i&gt; и &lt;i&gt;LayoutSubviews()&lt;/i&gt; у корневой &lt;i&gt;View&lt;/i&gt;, чтобы она перерисовалась с учетом последних изменений.&lt;/p&gt;
&lt;/div&gt;</description>
<pubDate>Mon, 23 Nov 2015 21:59:09 +0300</pubDate>
</item>

<item>
<title>Преобразование файла интерфейса Storyboard из формата экрана iPhone в iPad</title>
<guid isPermaLink="true">http://bibobo.ru/all/preobrazovanie-fayla-interfeysa-storyboard-iz-formata-ekrana-iph/</guid>
<link>http://bibobo.ru/all/preobrazovanie-fayla-interfeysa-storyboard-iz-formata-ekrana-iph/</link>
<comments>http://bibobo.ru/all/preobrazovanie-fayla-interfeysa-storyboard-iz-formata-ekrana-iph/</comments>
<description>&lt;p&gt;Рано или поздно при разработке приложения под iPhone возникает необходимость адаптировать интерфейс приложения под iPad. Если приложение большое создавать интерфейс под планшет с нуля может оказаться весьма накладным по времени и ресурсам делом.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Чтобы облегчить себе труд можно создать файл с интерфейсом для iPad из файла интерфейса iPhone. Для этого необходимо выполнить следующие шаги:&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Дублируем файл &lt;i&gt;MainStoryboard_iPhone.storyboard&lt;/i&gt; и переименовываем дубль в &lt;i&gt;MainStoryboard_iPad.storyboard&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;Открываем наш файл обычным текстовым редактором (рекомендую для этого использовать SublimeText2) и ищем следующий атрибут: &lt;i&gt;targetRuntime=«iOS.CocoaTouch»&lt;/i&gt;, дописываем в конце значения этого атрибута &lt;i&gt;“.iPad”&lt;/i&gt;, должно получиться вот так: &lt;i&gt;targetRuntime=«iOS.CocoaTouch.iPad»&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;Далее если присутствует тег &lt;i&gt;“simulatedScreenMetrics”&lt;/i&gt; удаляем в нем атрибут &lt;i&gt;“type”&lt;/i&gt; со значением &lt;i&gt;“retina4”&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;Далее пробегаемся автозаменой по файлу и заменяем значения ширин и высот экрана iPhone на значения экрана iPad (например width=«320» =&gt; width=«768», height=«568» =&gt; height=«1024»)&lt;/li&gt;
&lt;li&gt;Сохраняем &lt;i&gt;Storyboard&lt;/i&gt;, открываем ее в Interface Builder, корректируем положения экранов и лэйауты элементов&lt;br /&gt;
&lt;br/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Таким не хитрым образом мы быстро получили вариант интерфейса для iPad из интерфейса iPhone.&lt;/p&gt;
</description>
<pubDate>Wed, 11 Nov 2015 00:15:22 +0300</pubDate>
</item>


</channel>
</rss>