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

<channel>

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

<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>Реализация уведомлений-индикаторов для загружаемых на сервер файлов (Android)</title>
<guid isPermaLink="true">http://bibobo.ru/all/realizaciya-uvedomleniy-indikatorov-dlya-zagruzhaemyh-na-server/</guid>
<link>http://bibobo.ru/all/realizaciya-uvedomleniy-indikatorov-dlya-zagruzhaemyh-na-server/</link>
<comments>http://bibobo.ru/all/realizaciya-uvedomleniy-indikatorov-dlya-zagruzhaemyh-na-server/</comments>
<description>&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;ol start="1"&gt;
&lt;li&gt;Как создать уведомление для каждого загружаемого файла (если например существует возможность единовременно загружать несколько файлов)&lt;/li&gt;
&lt;li&gt;Как программно отменить уведомление после завершения процесса загрузки на сервер&lt;br /&gt;
&lt;input id="button_android_download_file_notification" type="button" onclick="toggle(cut_android_download_file_notification, button_android_download_file_notification)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div id="cut_android_download_file_notification" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Для решения этих проблем необходимо знать как система индексирует уведомления, создаваемые приложением. Каждое приложение может создать сколь угодно много уведомлений, но чтобы иметь возможность обновить определенное уведомление, либо отменить его — необходимо присвоить каждому из уведомлений свой уникальный ID. Ниже код реализации создания уведомления при старте загрузки файла на сервер и его отмены, по окончании загрузки:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;//Кеш, хранящий список id активных на данный момент уведомлений
private List&amp;lt;int&amp;gt; _notificationCache = new List&amp;lt;int&amp;gt;();

private void UploadFile()
{
    //получим рандомный ID для уведомления, при этом он не должен пересекаться с уже существующими (для этого используем кеш)
    Random r = new Random(System.Environment.TickCount);
    int notificationId = 0;
    while (notificationId == 0 || _notificationCache.Contains(notificationId))
    {
        notificationId = r.Next(1, 1000);
    }
    _notificationCache.Add(notificationId);
    //
                
    Notification.Builder builder = new Notification.Builder(Activity);
    builder.SetSmallIcon(Resource.Drawable.file_32x32)
        //устанавливаем заголовок для уведомления
        .SetContentTitle(filename)
        //в дескрипшне будет храниться наша строка &amp;quot;Загружаю файл на сервер...&amp;quot;
        .SetContentText(Activity.GetString(Resource.String.attachement_uploading))
        .SetAutoCancel(false)
        //запрещаем отмену уведомления пользователем
        .SetOngoing(true)
        //разрешаем уведомлению появиться в статус баре, мигать светодиодом и т.п.
        .SetDefaults(NotificationDefaults.All);

    NotificationManager manager = (NotificationManager)Activity.GetSystemService(Activity.NotificationService);
    manager.Notify(notificationId, builder.Build());

    //////////////////////////////////////////
    // ... код загрузки файла на сервер ... //
    //////////////////////////////////////////

    //отменяем уведомление и удаляем его ID из кеша
    manager.Cancel(notificationId);
    _notificationCache.Remove(notificationId);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
<pubDate>Sun, 20 Dec 2015 12:53:09 +0300</pubDate>
</item>

<item>
<title>Использование DownloadManager для скачивания файлов из сети (Android)</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-downloadmanager-dlya-skachivaniya-faylov-iz-seti-an/</guid>
<link>http://bibobo.ru/all/ispolzovanie-downloadmanager-dlya-skachivaniya-faylov-iz-seti-an/</link>
<comments>http://bibobo.ru/all/ispolzovanie-downloadmanager-dlya-skachivaniya-faylov-iz-seti-an/</comments>
<description>&lt;p&gt;В прошлой статье я рассказывал о том, как скачивать файлы из сети и сохранять их на диск, а так же использовать &lt;i&gt;MediaScanner&lt;/i&gt; для того, чтобы сделать файлы доступными другим приложениям (например галерее, если это фотография или видео).&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Сегодня же хотелось бы рассказать о более интересном и предпочтительном способе скачивания файлов — использовании системного сервиса &lt;i&gt;DownloadManager&lt;/i&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;DownloadManager это такой сервис платформы Android, который предоставляет весь спектр функциональности, связанной с загрузкой файлов. Он сам начнет скачивание, корректно отреагирует на обрыв связи или смену типа сети, запустит скачивание по новой (если был обрыв связи), создаст и отобразит уведомление о загрузке файла, уведомит &lt;i&gt;MediaScanner&lt;/i&gt; о том, что файл загружен и многое другое.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_android_download_save_file2" type="button" onclick="toggle(cut_android_download_save_file2, button_android_download_save_file2)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_android_download_save_file2" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Для корректного использования &lt;i&gt;DownloadManager&lt;/i&gt; нам понадобится:&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Создать свой класс — наследник &lt;i&gt;BroadcastReceiver&lt;/i&gt;, в котором мы будем реагировать на интенты, отсылаемые &lt;i&gt;DownloadManager&lt;/i&gt;’ом;&lt;/li&gt;
&lt;li&gt;Подготовить URI с ресурсом, который нужно загрузить;&lt;/li&gt;
&lt;li&gt;Создать реквест с нашим URI и другими настройками &lt;i&gt;DownloadManager&lt;/i&gt;;&lt;/li&gt;
&lt;li&gt;Запустить скачивание.&lt;br /&gt;
&lt;/br&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Итак перво наперво — напишем класс-наследник &lt;i&gt;BroadcastReceiver&lt;/i&gt; и реализуем логику, которая покажет Toast, когда наш файл скачается:&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 DownloadManagerReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        if (intent.Action == DownloadManager.ActionDownloadComplete)
        {
            //это не совсем правильно с точки зрения производительности, лучше передать ссылку на менеджер извне
            DownloadManager downloadManager = (DownloadManager)context.GetSystemService(Activity.DownloadService);

            long downloadId = intent.GetLongExtra(DownloadManager.ExtraDownloadId, -1);
            if (downloadId == -1)
            {
                return;
            }

            DownloadManager.Query query = new DownloadManager.Query();
            query.SetFilterById(new long[]{ downloadId });

            ICursor cursor = downloadManager.InvokeQuery(query);
            if (cursor != null &amp;amp;&amp;amp; cursor.MoveToFirst())
            {
                if (DownloadStatus.Successful == (DownloadStatus)cursor.GetInt(cursor.GetColumnIndex(DownloadManager.ColumnStatus)))
                {
                    string filename = cursor.GetString(cursor.GetColumnIndex(DownloadManager.ColumnTitle));
                    
                    Toast.makeText(context, string.Format(context.GetString(Resource.String.chat_attachement_succesfully_downloaded), filename), Toast.LENGTH_SHORT);
                }
            }

        }
    }
}&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;protected override async void OnCreate(Bundle bundle)
{
    //код инициализации активности
    //...
    /////////

    //создаем ресивер для корректной обработки успешного скачивания файла
    _downloadManagerReceiver = new DownloadManagerReceiver();
    RegisterReceiver(_downloadManagerReceiver, new IntentFilter(DownloadManager.ActionDownloadComplete));
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Последним этапом — необходимо получить ссылку на экземпляр сервиса &lt;i&gt;DownloadService&lt;/i&gt;, настроить менеджер и запустить скачивание:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;private void DownloadFile(string filename){
    DownloadManager downloadManager = (DownloadManager)GetSystemService(Activity.DownloadService);
    //реквест содержит URI файла и дополнительные настройки DownloadManager'а
    DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(&amp;quot;http://наш_url_для_скачивания&amp;quot;));

    //нотификейшн будет виден всегда, пока юзер не отменит его вручную или не кликнет
    request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
    //задаем публичную директорию с загрузками и вторым параметром имя файла + расширение
    request.SetDestinationInExternalPublicDir(Android.OS.Environment.DirectoryDownloads, filename);
    //разрешаем сканить новый файл МедиаСканнеру
    request.AllowScanningByMediaScanner();

    //запускаем скачивание
    downloadManager.Enqueue(request);   
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Вот и все, после старта скачивания менеджер создать нотификейшн со статусом скачивания, а после позволит открыть файл в приложении по-умолчанию.&lt;/p&gt;
&lt;/div&gt;</description>
<pubDate>Mon, 07 Dec 2015 22:12:40 +0300</pubDate>
</item>

<item>
<title>Сохранение файлов из сети на диск в публичную директорию (Android)</title>
<guid isPermaLink="true">http://bibobo.ru/all/sohranenie-faylov-iz-seti-na-disk-v-publichnuyu-direktoriyu-andr/</guid>
<link>http://bibobo.ru/all/sohranenie-faylov-iz-seti-na-disk-v-publichnuyu-direktoriyu-andr/</link>
<comments>http://bibobo.ru/all/sohranenie-faylov-iz-seti-na-disk-v-publichnuyu-direktoriyu-andr/</comments>
<description>&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;DownloadManager&lt;/i&gt; и доверить работу ему.&lt;br /&gt;
&lt;br/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;При этом скачанные файлы не будут видны приложениям на устройстве до тех пор, пока разработчик самостоятельно не сделает их доступными (либо до следующей перезагрузке устройства).&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;В этой статье мы рассмотрим ручной способ сохранения на диск применительно к Xamarin.Android. Более интересный вариант с использованием &lt;i&gt;DownloadManager&lt;/i&gt; будет рассмотрен в другой статье.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_android_download_save_file1" type="button" onclick="toggle(cut_android_download_save_file1, button_android_download_save_file1)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_android_download_save_file1" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Для начала необходимо получить массив байт файла из сети (например по его URL). Для этого можно использовать например простейший &lt;i&gt;WebClient&lt;/i&gt;, написать примерно такой код:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;string url = “url_к_нашему_файлу”;
byte[] dataBytes = new System.Net.WebClient().DownloadData(url);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Далее необходимо сохранить полученный массив байт как файл на диске. Для этого сначала необходимо получить путь до одной из публичных директорий, они могут быть следующими:&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Environment.DirectoryMusic;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryPodcasts;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryRingtones;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryAlarms;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryNotifications;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryPictures;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryMovies;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryDownloads;&lt;/li&gt;
&lt;li&gt;Environment.DirectoryDcim.&lt;br /&gt;
&lt;br/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Назначение директорий вполне ясно из их названия, будем использовать &lt;i&gt;Environment.DirectoryDownloads&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;//получаем путь к директории с загрузками
string dirPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
//если директории нет (такое может быть, да О_о, создадим ее)
if (!Directory.Exists(dirPath))
{
    Directory.CreateDirectory(dirPath);
}

//получаем путь до конечного файла в нашей директории, если он уже есть - удалим его
string filePath = Path.Combine(dirPath, file.Name);
if (File.Exists(filePath))
{
    File.Delete(filePath);
}

//записываем массив байт на диск
File.WriteAllBytes(filePath, fileBytes);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Почти готово, осталось только объявить системе о том, что на диске теперь есть новый файл, доступный всем вокруг. Для этого используется подсистема Android, которая называется &lt;i&gt;MediaScanner&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;//объявим МедиаСканеру о том, что появился новый файл
Intent mediaScannerIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScannerIntent.SetData(Android.Net.Uri.FromFile(new Java.IO.File(filePath)));
SendBroadcast(mediaScannerIntent);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
<pubDate>Wed, 02 Dec 2015 22:06:16 +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>Использование Fragment без UI для хранения данных</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-fragment-bez-ui-dlya-hraneniya-dannyh/</guid>
<link>http://bibobo.ru/all/ispolzovanie-fragment-bez-ui-dlya-hraneniya-dannyh/</link>
<comments>http://bibobo.ru/all/ispolzovanie-fragment-bez-ui-dlya-hraneniya-dannyh/</comments>
<description>&lt;p&gt;Fragment — это сущность, позволяющая инкапсулировать часть интерфейса и его логику для переиспользования. Фрагменты были представлены в Android 3.0 и позволили существенно упростить работу по проектированию интерфейса для устройств с различными размерами экранов (так называемая парадигма Responsive Design, когда одно и то же приложение выглядит по разному на устройствах с различной диагональю экрана).&lt;/p&gt;
&lt;p&gt;&lt;input id="button_using_fragment_without_ui" type="button" onclick="toggle(cut_using_fragment_without_ui, button_using_fragment_without_ui)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_using_fragment_without_ui" 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;ol start="1"&gt;
&lt;li&gt;Очень похожи на Activity и имеют схожий цикл жизни, при этом дополненный несколькими событиями;&lt;/li&gt;
&lt;li&gt;Как и Activity инкапсулируют логику работы с интерфейсом;&lt;/li&gt;
&lt;li&gt;Могут жить только внутри Activity, при этом один и тот же фрагмент может быть использован в различных Activity, что существенно упрощает задачу при проектировании интерфейса для различных устройств.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Фрагменты обладают ещё одним свойством, которое может быть весьма полезно при разработке приложений — фрагмент можно создать вовсе без пользовательского интерфейса и заставить его не умирать при пересоздании родительской Activity. Это свойство может быть очень к месту для хранения источника данных (модели) в таком фрагменте.&lt;/p&gt;
&lt;p&gt;Для многих начинающих разработчиков, пришедших с десктопных платформ, может стать настоящим откровением поведение Android например при изменении ориентации устройства — в таком случае Activity будет уничтожена и создана вновь с лэндскейпным лэйаутом. Соответственно если к Activity привязан какой-либо источник данных — он будет так же пересоздан (или перезагружен, в случае ContentProvider).&lt;/p&gt;
&lt;p&gt;Чтобы этого не произошло можно создать специальный фрагмент без UI и передать в его конструкторе значение true в сеттер setRetainInstance (Java), либо установить значение true свойству RetainInstance (C#). Далее инициализировать и сохранить в нём нашу модель.&lt;/p&gt;
&lt;p&gt;Все, после подобных манипуляций получим постоянный доступ к модели с сохранением её состояния даже при пересоздании родительской Activity.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Ниже небольшой пример кода на C# и Xamarin:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// Фрагмент без интерфейса, будет хранить нашу модель
/// (данный фрагмент будет ретейниться после уничтожения основной вьюхи и наши данные не будут перегенериваться при пересоздании Activity)
/// &amp;lt;/summary&amp;gt;
public class ModelHolderFragment : Android.App.Fragment{
	public static string SearchTag = &amp;quot;MODEL_HOLDER_FRAGMENT&amp;quot;;

	public ViewPagerItem[] DataSourceItems { get; private set;}

	public override void OnCreate (Bundle savedInstanceState)
	{
		base.OnCreate (savedInstanceState);

		DataSourceItems = GenViewPagerItems ();

		//установим флаг, который запретит фрагменту умирать при уничтожении основной вьюхи
		RetainInstance = true;
	}

	/// &amp;lt;summary&amp;gt;
	/// Генерация фейковых данных
	/// &amp;lt;/summary&amp;gt;
	/// &amp;lt;returns&amp;gt;The view pager items.&amp;lt;/returns&amp;gt;
	private ViewPagerItem[] GenViewPagerItems(){
		...
		return items;
	}
}

///Хэндлер события OnCreate родительской Activity - тут создадим экземпляр нашего фрагмента
protected override void OnCreate(Bundle savedInstanceState) {
	base.OnCreate(savedInstanceState);
	SetContentView (Resource.Layout.activity_main);

	//инициализируем фрагмент без интерфейса для хранения модели только при первом создании вьюхи
	if (savedInstanceState == null) {
		FragmentManager.BeginTransaction ().Add ((Android.App.Fragment)new ModelHolderFragment (), ModelHolderFragment.SearchTag).Commit();
		FragmentManager.ExecutePendingTransactions ();
	}
	_model = (ModelHolderFragment)FragmentManager.FindFragmentByTag (ModelHolderFragment.SearchTag);
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
<pubDate>Fri, 19 Jun 2015 21:01:03 +0300</pubDate>
</item>

<item>
<title>Использование паттерна ViewHolder</title>
<guid isPermaLink="true">http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</guid>
<link>http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</link>
<comments>http://bibobo.ru/all/ispolzovanie-patterna-viewholder/</comments>
<description>&lt;p&gt;С этой статьи я бы хотел начать цикл кратких заметок о разработке под мобильные устройства. Приложения под телефоны и планшеты — мое давнее увлечение. К сожалению плотно заняться которым, в силу различных обстоятельств, я смог только сейчас. В прочем хватит лирики, начнём.
В качестве платформы я выбрал Android, как наиболее массовую и открытую систему. В качестве языка программирования сегодня будем использовать C#, писать будем на Xamarin с использованием Android API.&lt;/p&gt;
&lt;p&gt;&lt;input id="button_using_viewholder" type="button" onclick="toggle(cut_using_viewholder, button_using_viewholder)" value="Читать дальше" hiddenText="Свернуть" visibleText="Читать дальше"&gt;&lt;/p&gt;
&lt;div id="cut_using_viewholder" style="display: none;" displayOld=""&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Практически любое мобильное приложение это некий экран с набором однотипных информационных элементов, для отображения которых обычно используется список или какой-либо грид. Из-за того, что платформа мобильная и работает на устройстве с небольшим объёмом памяти и слабым процессором (хаха! В моем первом компьютере было памяти раз в 15 меньше и процессор существенно слабее, чем сейчас в среднем телефоне) операционная система вынуждена ресурсы экономить. Соответственно если речь идёт о списке (ListView в Android), а если точнее об адаптере, который предоставляет данные для списка (например ArrayAdapter в Android) мы сталкиваемся с одной из особенностей, вызванных необходимостью беречь ресурсы.&lt;/p&gt;
&lt;p&gt;Такая особенность заключается в том, что лист не создаёт ячейки под каждую из записей подключенного источника данных. Он создаёт лишь то количество ячеек, сколько может уместиться на экране в данный момент плюс по одной лишней ячейке сверху и снизу. Такое поведение характерно не только для Android, но и для iOS.&lt;/p&gt;
&lt;p&gt;При этом когда пользователь прокручивает список система не удаляет ячейки, она старается их переиспользовать.&lt;/p&gt;
&lt;p&gt;Когда ячейка пропадает из поля видимости пользователя она передаётся в метод GetView() адаптера, в котором происходит заполнение ячейки данными. В этом методе можно решить, переиспользовать уже готовую ячейку или создать новую.&lt;/p&gt;
&lt;p&gt;Все бы ничего, но если лэйаут ячейки сложный то мы напрямую сталкиваемся с необходимостью проводить поиск элементов в лэйауте (TextView, ImageView и т. п.) для заполнения их актуальными данными. Вроде бы ничего страшного, вызвал &lt;i&gt;FindViewById()&lt;/i&gt; с нужным id элемента, получил его и нет проблем, но выполнение метода &lt;i&gt;FindViewById()&lt;/i&gt; это длительная операция, потому что она тянет за собой траверс по XML-коду лэйаута нашей ячейки. В итоге на сложных лэйаутах имеем просадку по производительности скроллинга списка.&lt;/p&gt;
&lt;p&gt;Чтобы решить данную проблему нам необходимо использовать паттерн &lt;b&gt;ViewHolder&lt;/b&gt;. Кратко его суть заключается в следующем:&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Реализуем класс, который в конструкторе принимает нашу &lt;i&gt;View&lt;/i&gt;, делает траверс и ищет все её элементы, которые нам потребуются для заполнения данных;&lt;/li&gt;
&lt;li&gt;Записываем ссылки на эти элементы в соответствующие поля класса, снабжаем их геттерами, чтобы потом к ним обращаться;&lt;/li&gt;
&lt;li&gt;При создании нашей &lt;i&gt;View&lt;/i&gt; — создаём наш &lt;i&gt;класс-ViewHolder&lt;/i&gt; и записываем ссылку на него в свойстве &lt;i&gt;Tag&lt;/i&gt; у нашей &lt;i&gt;View&lt;/i&gt;;&lt;/li&gt;
&lt;li&gt;Теперь при попытке переиспользовать &lt;i&gt;View&lt;/i&gt; нам не нужно делать повторный поиск элементов в лэйауте, достаточно получить ссылку на &lt;i&gt;ViewHolder&lt;/i&gt; из свойства &lt;i&gt;Tag&lt;/i&gt; и обратиться к соответствующим геттерам холдера;&lt;/li&gt;
&lt;li&gt;PROFIT.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Если делать реализацию на Java сложностей возникнуть не должно, но если использовать C# и Xamarin мы столкнемся с корнями, растущими из Java :-)&lt;/p&gt;
&lt;p&gt;Предположим есть такой класс:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public class MyViewHolder{
public TextView MyTextView {get; private set;}

public MyViewHolder(View view){
...
}
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Если теперь попытаться присвоить ссылку на него свойству &lt;i&gt;Tag&lt;/i&gt; нашей &lt;i&gt;View&lt;/i&gt; у нас ничего не получится. Поскольку &lt;i&gt;Tag&lt;/i&gt; принимает объект типа &lt;i&gt;Java.Lang.Object&lt;/i&gt;, а наш класс наследован от типа &lt;i&gt;Object&lt;/i&gt;. Прямое кастование к нужному типу так же не приведёт к успеху. Кастование сначала к &lt;i&gt;Object&lt;/i&gt;, а потом к &lt;i&gt;Java.Lang.Object&lt;/i&gt; не даст ошибки компиляции, зато упадёт в рантайме.&lt;/p&gt;
&lt;p&gt;Что же делать? &lt;b&gt;Все просто :-) наследовать наш класс от типа &lt;i&gt;Java.Lang.Object&lt;/i&gt; и будет счастье!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Небольшой пример того, как все это работает:&lt;/p&gt;
&lt;pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code&gt;public class TimetableItemAdapter : ArrayAdapter&amp;lt;TimetableItem&amp;gt;
{
	/// &amp;lt;summary&amp;gt;
	/// Класс ViewHolder, нужен что бы исключить множественный траверс по элементам вьюхи, для производительности
	/// &amp;lt;/summary&amp;gt;
	public class TimetableItemViewHolder : Java.Lang.Object{
		public TextView TimeText { get; private set;}
		public TextView DescriptionText { get; private set;}
		public ImageView AcceptButton { get; private set;}

		public TimetableItem BoundedItem { get; set;}

		public TimetableItemViewHolder(View view, TimetableItem item){
			TimeText = view.FindViewById&amp;lt;TextView&amp;gt;(Resource.Id.timetable_time_text);
			DescriptionText = view.FindViewById&amp;lt;TextView&amp;gt;(Resource.Id.timetable_description_text);
			AcceptButton = view.FindViewById&amp;lt;ImageView&amp;gt;(Resource.Id.timetable_accept);

			BoundedItem = item;
		}
	}

	private MainActivity _context;

	public TimetableItemAdapter(Context context, int resource, IList&amp;lt;TimetableItem&amp;gt; objects) : base(context, resource, objects) {
		_context = (MainActivity)context;
	}
			
	public override View GetView(int position, View convertView, ViewGroup parent) {
		View resultView = null;
		TimetableItemViewHolder holder = null;
		TimetableItem item = GetItem(position);

		if(convertView != null){
			resultView = convertView;
			holder = (TimetableItemViewHolder)convertView.Tag;
			holder.BoundedItem = item;
		}else{
			resultView = ((LayoutInflater)_context.GetSystemService(Context.LayoutInflaterService))
				.Inflate(Resource.Layout.timetable_list_item, parent, false);
			holder = new TimetableItemViewHolder(resultView, item);
			resultView.Tag = holder;
		}

		if(item != null &amp;amp;&amp;amp; holder != null){
			holder.TimeText.Text = item.Time;
			holder.DescriptionText.Text = item.Description;
		}

		return resultView;
	}
}&lt;/code&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;p&gt;Во &lt;i&gt;ViewHolder&lt;/i&gt; не обязательно хранить только ссылки на элементы лэйаута, иногда может потребоваться хранить и какие-либо сопутствующие данные. Например в коде выше во &lt;i&gt;ViewHolder&lt;/i&gt; записывается дополнительно ссылка на элемент из источника данных, для последующего использования где-нибудь. Но в данном случае нужно помнить, что при переиспользовании &lt;i&gt;View&lt;/i&gt; нужно не забыть записать в свойство ссылку на новый элемент источника данных.&lt;/p&gt;
&lt;/div&gt;</description>
<pubDate>Wed, 17 Jun 2015 10:56:43 +0300</pubDate>
</item>


</channel>
</rss>