Pull to refresh

Укрощаем пользовательский интерфейс на iPhone с MonoTouch.Dialog

Reading time 5 min
Views 1.4K
Original author: Miguel de Icaza
monotouchВ основе пользовательского интерфейса лежит UITableView, мощный виджет по отрисовке таблиц, который использует, почти, каждое приложение на iPhone. UITableView — мощный виджет, которые умеет отрисовывать данные различными способами, основываясь на том, как вы настроили сам виджет.

Вот вам пример всевозможных видов UITableView:

image

Cодержимое UITableView рендерится вызовом кода, который пишет разработчик, который предоставляет данные по требованию. Протокол включает в себя такие запросы, как: «Сколько секций?», «Сколько строк в секции N?», «Какой заголовок секции N?» в виде колбэков для предоставления актуального содержимого ячейки. Не смотря на всю мощь виджета, создавать UI с его помощью довольно проблематично. Разработчики тратят слишком много времени, повторяя свои действия, настраивая каждое представление, скудная конфигурация и нужда в шлифовании некоторых настроек. Портируя многие примеры с Object-C на C# я находил нужный вариант повторяя один и тот же процесс снова и снова.

На моих пальцах появились мозоли, но даже по ночам я думал, что нашел не идеальное решение и существует вариант по лучше. Но в то время, я делал простое построчное портирование, я был не готов создать новое API поверх всего этого.

Недавно, когда мой любимый твиттер-клиент на iPhone испоганил свой UI, я решил написать свой собственный Twitter-клиент. Первым шагом было создать настройки для моего твиттер-аккаунта. Как вы поняли, это реализовывается через UITableView. Мне пришлось настроить модель, отвечающую на события представления, switch'и и if'ы были тут и там, вообщем никакого наслаждения от написания кода. Вот так и родился MonoTouch.Dialog.

Я хотел, чтобы с помощью рефлекции класс мог привязаться к диалоговому окну, что-то, что позволило бы мне написать C# класс и привязать его к UITableView:
class TwitterConfig {
  [Section ("Account")]
   [Entry] string Username;
   [Password] string Password;

  [Section ("Settings")]
   bool AutoRefresh;
   bool AutoLoad;
   bool UseTwitterRetweet;
}


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

Код движка построен по принципу — каждая строка может быть виджетом. Она может содеражить текст, переключатель, текстовое поле, слайдер, календарь или любой созданный пользователем элемент управления. Я назвал это «Elements» и создал следующие:
  • BooleanElement — отрисовка с помощью UISwitch
  • FloatElement — слайдер
  • HtmlElement — при нажатии запускает веб-браузер
  • StringElement — отображает простой текст
  • MultilineElement — многострочный текст
  • RadioElement — единичный выбор со списка
  • CheckboxElement — как BooleanElement, но вместо UISwitch использует checkbox
  • ImageElement — позволяет пользователю выбрать картинку или сделать фотографию
  • EntryElement — текстовое поле
  • DateTimeElement, DateElement, TimeElement — выбор даты/дат и времени

MonoTouch.Dialog следует рекомендациям Apple HIG для iPhone, давая возможность максимально сфокусироваться на приложении, а не на его мелочах.

Также, UITableView построен на основе MVC, которая позволяет эффективно масштабировать большие наборы данных, большинство страниц с настройками и данными не требуют такой сложности.

Другой возможностью является, решение всех проблем с вводом текста: ввод текста, автоматический переход на следующую строку при нажатии Enter, выравнивание всех линий в секции, скрытие клавиатуры при достижении конца ввода.

Пример API в действии:

imagevar root = new RootElement ("Settings") {
 new Section (){
  new BooleanElement ("Airplane Mode", false),
 new RootElement ("Notifications", 0, 0) { Notifications }
 new Section (){
  new RootElement ("Sound"), { Sound },
  new RootElement ("Brightness"){ Brightness },
  new RootElement ("Wallpaper"){ Wallpaper }
 },
 new Section () {
  new EntryElement ("Login", "Your login name", "miguel"),
  new EntryElement ("Password", "Your password", "password", true),
  new DateElement ("Select Date", DateTime.Now),
  new TimeElement ("Select Time", DateTime.Now),
 }
}

Создав RootElement, вы можете передавать его в DialogViewController для управления:
var dv = new DialogViewController (root);
navigation.PushViewController (dv, true);

Reflection API


API реплекции проверяет класс на наличие полей, к которым привязаны специальные атрибуты.

Пример класса и как он отрисовывается:

imageclass AccountInfo {
  [Section]
  public bool AirplaneMode;

  [Section ("Data Entry", "Your credentials")]

  [Entry ("Enter your login name")]
  public string Login;

  [Caption ("Password"), Password ("Enter your password")]
  public string passwd;

  [Section ("Travel options")]
  public SeatPreference preference;
}
  

Как вы заметили, энумераторы(SeatPreference) автоматически преобразуются в radio, которое использует UINavigationController для управления, а заголовки берутся из имен полей, данное поведение можно настроить с помощью атрибута [Caption].

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

LINQ и MonoTouch.Dialog


Крэйг написал отличное приложение для конференции для Mix 2010. Я помог ему уменьшить количество кода, удалив весь повторяющийся код, чтобы установить UITableView для различных частей приложения для MonoTouch.Dialog. Так как приложение конференции работает с расписанием в базе данных, я расширил MonoTouch.Dialog для улучшения работы с LINQ.

В том же духе, как и с System.Xml.Linq API, который позволяет вам создавать XML документы с вложенными LINQ-определениями, вы можете использовать MonoTouch.Dialog для создания UI.

Для приложения Крэйга, я написал SessionElement, который позволяет запустить сессии и показывать заголовок и местоположение сессии.

Следующий код содержит UI с закладки «My Schedule». Данные запрашиваются по требованию (Apple рекомендует «ленивую» загрузку для всех представлений)
imagepublic class FavoritesViewController : DialogViewController {
 public FavoritesViewController () : base (null) { }

 public override void ViewWillAppear (bool animated)
 {
  var favs = AppDelegate.UserData.GetFavoriteCodes();
  Root = new RootElement ("Favorites") {
   from s in AppDelegate.ConferenceData.Sessions
    where favs.Contains(s.Code)
    group s by s.Start into g
    orderby g.Key
    select new Section (MakeCaption ("", g.Key)) {
     from hs in g
      select (Element) new SessionElement (hs)
    }
  };
 }
}

Так что используйте любую из двух моделей, которая вам больше нравится: Reflection для быстрой и простой работы с интерфейсом и данными или Element API для более продвинутой настройки пользовательского интерфейса, не тратя полжизни на написание boilerplate кода

Я надеюсь, что все это помогло вам, ребята, потратить больше времени на улучшения своих приложений и меньше времени на написания рутинного кода.

MonoTouch.Dialog не идеален и не содержит всех пожеланий. Также я приветствую дополнения, вы не скованы ничем в данной ветке кода и вносите любые изменения, которые покажутся вам нужными.
Tags:
Hubs:
+16
Comments 17
Comments Comments 17

Articles