Результати пошуку за запитом: mvc 5*
10 секретів ідеального коду
Автор: Nicolas Baptiste
Взято с настоящего поля битвы: работы
Спустя практически 10 лет работы на Javascript мне захотелось поделиться своими ежедневными секретами написания кода. Многие советы были позаимствованы у моих коллег, из книг или видео. Такие приёмы не должны оставаться в тайне, поэтому я ими и хочу поделиться!
1. Меньше кода = меньше багов
Это может прозвучать тривиально, но всегда держите это в уме. Любую часть можно убрать, даже не сомневайтесь! Просто удалите. Неработающий код, неиспользуемые переменные, лишние скобки: каждый малейший знак учитывается.
2. Развивайте читабельность кода
Это дополнение к пункту 1. Если хочется сократить код, всегда нужно помнить о том, что рано или поздно код будет прочитан другими людьми. Может, кто-то другой, может, через несколько месяцев, но будет очень неприятно, если придется потратить больше времени на разбор кода, чем было потрачено на его написание. Запомните: код всегда важнее, чем комментарии или документация.
Следите за переменными и именами функций, они должны быть достаточно наглядными, чтобы потом не объяснять их в комментариях.
3. Не пугайтесь функций, возвращающих функции
Когда я начал кодить на javascript, мне не нравился метод написания кода с использованием функций, возвращающих функции, потому что, казалось, этому сложно следовать в процессе выполнения. Но этот шаблон, довольно мощный и часто используемый, называется замыканием. Это действительно полезная функция и, как только вы используете её, она значительно облегчает структуру кода. И это первый шаг для погружения в функциональное программирование.
4. Извлекание частей
Функция становится слишком большой? Сделайте из нескольких частей отдельные функции! Не совсем понятно? Выделите их в переменную и дайте ей понятное имя. Такой метод, конечно, схож со вторым пунктом, но это сделает ваш код более читабельным. А знаете ли Вы, что Ваша среда разработки может помочь? Ctrl + alt + v выражение извлекается в Webstorm и потом выделяется в переменную. Ctrl + alt + m сделает из него функцию.
5. Переверните мышление
Это первый шаг перед TDD. Скорее всего, вы всегда знаете, чего хотите достичь. Так начните с конца, напишите ожидаемый результат и пишите код в обратном порядке, чтобы получить то, что хотите. Если будете писать в прямом порядке, вероятней всего, Вы начнете писать и проверять, работает ли оно. Поменяйте, проверьте, напишите что-то другое, проверьте и…. Возможно, это даже не особо полезно для достижения Вашей цели. Но такое мышление поможет сфокусироваться и потратить меньше времени.
6. Исключите условные операторы
Если для функции у вас на уме принцип единственной ответственности, условным операторам тут не место. Они создают ветвления в потоке выполнения. И один маленький if позволит другим разработчикам добавить всё больше логики в него. Я считаю, что условные операторы if вообще не должны использоваться без else, а их размещение внутри другого условного оператора нужно полностью запретить! Опять же, такой подход делает код более сложным, и часто оказывается, что функция выполняет больше, чем нужно. Как же от этого избавиться? Смотрите следующий пункт.
7. Используйте lodash_.get вместо проверки null/undefined
Очень часто, анализируя код, я вижу использование ifs для проверки null или undefined значений. Но без использования else! То есть идёт работа только с одним путём, а другой путь остаётся местом загадок (и багов!).
Тут на помощь приходит Lodash (лучшее, написанное когда-либо на javascript) с его замечательной функцией get. Её первым аргументом является объект, над которым вы работаете, вторым – путь к дочерним атрибутам, которые вы хотите получить, и третьим является значение по умолчанию в случае с undefined.
Два преимущества использования всего одной функции!
8. Используйте тернарные операторы
Кто-то может поспорить насчёт их читабельности, но если правильно извлекать функции, они станут превосходным инструментом. Они также заставят Вас сотрудничать с else case. Плюс, они не позволяют содержать в себе больше кода.
Чем меньше ваш код похож на смертельную пирамиду, тем лучше.
9. Никаких больше for loops, только map, reduce и filter
Мы, наконец-то, подошли тут к теме функционального программирования, так как эти методы являются основой этого стиля. Хоть названия или теории не особо известны, они действительно помогут избежать тяжело читаемого императивного стиля для выражений for.
Эти методы сейчас уже встроены в большинство браузеров, так что используйте их! Но если вам всё же нужна совместимость, можно использовать lodash аналоги.
Итак, когда их использовать?
Выражение for, имеющее дело со всеми точками входа – это map
for + if , вероятно, filter
sum или accumulation - это просто reduce
С помощью сочетания этих методов вы можете сделать много работы в хорошей и краткой форме.
10. Читайте исходники
Если документации недостаточно - читайте исходники! Это поможет Вам узнать, как их использовать и даст неплохое представление об их качестве.
Плюс, некоторые библиотеки имеют исходники, встроенные в их документацию. Так с ними даже быстрее можно ознакомиться. С нашей IDE можно часто обращаться к источнику с помощью ctrl + clic на имя метода.
Это отличный способ убрать магию и набраться вдохновения для собственного кода!
В заключение хотелось бы сказать, что это те принципы, которые помогают лично мне. Жаль, что я не знал их раньше, например, в школе… Зато ты знаешь их сейчас. И ими можно пользоваться практически в любом контексте. Некоторые из них распространяются не только на javascript, так что пользуйтесь ими где угодно :)
Также хотелось бы обратить ваше внимание на великого Uncle Bob и его сайт . Вам определённо стоит посмотреть эти видео, даже несмотря на то, что они по большей части касаются Java, идеи применимы и в Javascript.
Переведено с источника.
Введення в розробку програм під iOS. Частина 7
Автор: Volodymyr Bozhek
Здравствуйте, дорогие читатели.
В этом уроке мы:
выполним рефакторинг проекта “Warehouse” под “iOS”. Создадим удобную структуру расположения модулей в проекте.
научимся пользоваться библиотекой “Alamofire”.
научимся сохранять объекты в настройки телефона и извлекать их.
реализуем функциональность для работы со всеми сервисами, созданными в данном уроке.
Откройте проект “Warehouse” под iOS, который мы делали на протяжении всех уроков.
Запросы на сервер можно отправлять синхронно и асинхронно. Основное требование Apple, чтобы приложение не зависало и большие операции выполнялись в фоновом режиме.
Представим ситуацию: у нас мобильный интернет “GPRS EDGE”, скорость соединения около 54 кб/сек. Если отправлять запрос на получение данных на сервер синхронно, мы блокируем работу приложения и пользователь вынужден ждать. Наша задача - дать пользователю выбор, ждать или нет. Поэтому лучше всего отправлять такой запрос асинхронно.
На сегодняшний день существует такая библиотека “Alamofire”, где собраны лучшие практики асинхронного программирования и все оптимизировано за нас.
Откройте браузер и перейдите по адресу: “https://github.com/Alamofire/Alamofire”. На сайте нажмите кнопку “Clone of download” и в появившемся окне нажмите “Download Zip”.
Когда скачается архив, распакуйте его.
Выделите файл “Alamofire.xcodeproj” и папку “Source” и скопируйте их в папку проекта “Warehouse”.
Перейдите в проект “Warehouse”.
В панели навигации выполните контекстное меню по папке “Warehouse” и в контекстном меню выберите “Add files to 'Warehouse'...”. В открывшемся диалоговом окне выбора файлов выберите файл “Alamofire.xcodeproj”.
Выполните в меню “Product -> Build”, чтобы собрать проект.
Затем выделите в панели навигации папку “Warehouse”, в области содержимого откроются свойства проекта. Откройте вкладку “General”, пролистайте область содержимого в самый низ.
Установите свойство “Embedded binaries”, нажмите плюсик. В появившемся диалоговом окне, выберите “Alamofire.Framework.iOS” и нажмите кнопку “Add”.
Результат должен получиться таким.
Выполните в меню “Product -> Build”, чтобы собрать проект.
Итак, что мы только что сделали. Мы подключили исходники библиотеки “Alamofire” в проект “Warehouse”. Можно было это сделать и отдельным проектом, но так проще для восприятия. Затем мы подключили сборку “Alamofire.framework.iOS” в приложение “Warehouse” для возможности обращения к классам данной библиотеки из нашего проекта.
Теперь нам надо создать модели данных, в которые мы будем сохранять JSON объекты, полученные с сервера. Также необходимо предусмотреть удобство сохранения и извлечения данных, поскольку запросы будут асинхронными и просто вернуть нужный экземпляр из метода будет проблематично.
Именно поэтому мы поступим следующим образом.
Отправляем запрос на сервер асинхронно. В метод отправки добавляем callback метод, который вызовется после того, как модель была успешно сохранена в настройки телефона.
Ждем ответа. Когда ответ пришел, десериализуем объект JSON в нужную модель данных, объявленную в приложении.
Сохраняем заполненную модель в настройки телефона.
В контроллере, из которого был отправлен запрос, в callback методе извлекаем модель из настроек телефона и инициализируем данными элементы управления.
Создайте следующую структуру папок в панели навигации.
В папку “Warehouse” были добавлены папки “Controllers”, “Protocols”, “Models”, “Services”.
В папку “Controllers” были добавлены папки “Users”, “Registration”, “Authorization”, “Supplies”.
В папке “Controllers” будут храниться модули контроллеров.
В папке “Protocols” будут храниться модули протоколов.
В папке “Models” будут храниться модули моделей данных.
В папке “Services” будут храниться модули обращения к REST сервисам.
Сейчас нам необходимо провести некоторую подготовку, перед тем как начать реализовывать приложение. Пока, давайте, добавим нужные модули. Сам по себе добавленный модуль ничего не делает.
Перетащите в папку “Controllers / Supplies” модули “SuppliesViewController.swift”, “ProductViewController.swift”.
Перетащите в папку “Controllers / Authorization” модуль “ViewController.swift”.
Добавьте в папку “Controllers / Registration” модуль “RegistrationViewController.swift”.
Добавьте в папку “Controllers / Supplies” модуль “ImageCollectionViewController.swift”.
Добавьте в папку “Controllers / Users” модули “UsersViewController.swift”, “UserItemViewController.swift”.
Добавьте в папку “Protocols” модуль “SelectedImageProtocol.swift”.
Добавьте в папку “Models” модули “ProductModel.swift”, “UserModel.swift”.
Добавьте в папку “Services” модули “UserData.swift”, “ProductData.swift”, “Services.swift”, “AuthorizationService.swift”, “ProductService.swift”, “UserService.swift”.
У вас должна получиться следующая структура.
Как видите, проект стал больше. Он станет еще больше, как только мы заполним добавленные модули.
Откройте модуль “Models / UserModel.swift”. Заполните его, как показано ниже.
В этом модуле мы создаем модель пользователя, в которую будем десериализовывать JSON объект пользователя, полученный от сервера.
В классе “UserModel” мы добавляем поля модели “_id”, “userName”, “userPwd”, “userDesc”, которые декларировали при объявлении схемы базы данных “Mongo DB” на сервере.
На 9 строке мы подключаем пространство имен “Foundation”.
На 11 строке мы объявляем класс с именем “UserModel”, который наследует класс “NSObject”, являющийся базовым классом для всех классов в iOS платформе. Наследуемся от протокола “NSCoding”, данный протокол необходимо реализовать, чтобы объект можно было сохранять в настройках телефона и извлекать оттуда.
Рассмотрим протокол “NSCoding”.
Протокол содержит метод “encode” и инициализатор.
Экземпляр класса “NSCoder” позволяет кодировать и раскодировать свойства класса, значения которых могут сохраняться в хранилище настроек телефона.
В методе “encode” данные извлекаются из свойств и кодируются в формат, нужный для сохранения в настройки телефона.
В инициализаторе данные извлекаются и раскодируются из настроек телефона, затем сохраняются в нужные свойства класса.
Вот так это работает.
Теперь вернемся и рассмотрим дальше модуль “UserModel.swift”.
На 12 строке мы объявляем поле “_id” типа “String” и инициализируем его значением по умолчанию, пустая строка.
На 13 строке мы объявляем поле “userName” типа “String” и инициализируем его значением по умолчанию, пустая строка.
На 14 строке мы объявляем поле “userPwd” типа “String” и инициализируем его значением по умолчанию, пустая строка.
На 15 строке мы объявляем поле “userDesc” типа “String” и инициализируем его значением по умолчанию, пустая строка.
На 17 строке мы переопределяем инициализатор по умолчанию для класса “NSObject”, тем самым мы говорим, что создание объекта без передачи аргументов в инициализатор будет возможно.
На 18 строке мы вызываем базовый инициализатор из класса “NSObject”.
На 21 строке мы объявляем параметризированный инициализатор. Данный инициализатор инициализирует поля класса “_id”, “userName”, “userPwd”, “userDesc” значениями, переданными в аргументы инициализатора.
На 31 строке мы реализуем конструктор протокола “NSCoding”. Ключевое слово “required” обозначает, что вызов этого инициализатора обязателен в первую очередь. Ключевое слово “convenience” гарантирует то, что после вызова этого инициализатора будут вызваны остальные инициализаторы классов, связанных с этим классом по цепочке вверх.
На 32 строке мы раскодируем из настроек телефона значение свойства “_id” путем вызова на экземпляре “aDecoder” типа “NSCoder” метода “decodeObject”, аргумент “forKey” которого принимает название поля/свойства, которое было сохранено в настройки телефона.
Этот метод возвращает значение “Any?”, т.е. неопределенный тип (аналог в C# - это Object, в Visual Basic - это Variant). Нам необходимо это значение вручную привести к нужному типу поля.
В настройках телефона содержится большой файл, который имеет древовидную структуру словаря ключ и значение.
Чтобы привести к нужному значению, мы используем конструкцию приведения к типу “as”. Причем, мы можем управлять этой конструкцией.
Например, нам надо привести тип “Any?” к типу “String”. Для этого мы используем конструкцию “as!”.
Если же нам надо привести к типу “String?” (nullable тип), тогда мы используем конструкцию “as?”. Зачем мы это делаем.
Настройки телефона разбиваются на разные файлы, для каждого приложения используется свой файл с настройками.
Когда приложения устанавливаются на macOS или iOS платформу, они устанавливаются в виде папки и имеют тип “Bundle”.
Apple довольно кардинально подошла к этому вопросу, она сделала так, что все настройки для каждого приложения хранятся внутри “Bundle” данного приложения. И когда приложение удаляется, удаляется “Bundle” со всеми настройками с диска операционной системы и не остается никаких следов после удаления.
Чего не скажешь о таких платформах, как Windows и Android, которые засоряют все пространство на диске операционной системы вокруг папки, в которую было установлено приложение, и выше. И когда приложение удаляешь, есть риск, что после этого будет необходимо еще и переустановить операционную систему.
Отчасти именно из-за этого подхода техника Apple пользуется такой популярностью и их устройства выигрывают по отказоустойчивости программного обеспечения по сравнению с конкурентами.
Если приложение обновляется, то файл настроек остается старый. Чтобы обновить его, надо выполнить запись в него, тогда старые конструкции удалятся, а новые добавятся. Представим себе ситуацию, сейчас в нашей модели “UserModel” всего 4 поля. Мы сохранили эту модель в файл настроек телефона. Отправили в магазин, пользователи скачали, установили приложение.
Вышла новая версия приложения, в которой мы добавили новое поле в эту модель с именем “userRole” типа “String”.
Разумеется, первая операция, которая будет выполнена как обычно - это запрос данных из настроек телефона, но для поля “userRole” установлено значение “nil” (аналог в C# null) и произойдет ошибка и креш приложения, так как мы не обработали эту ситуацию, а пытаемся через метод “decodeObject” получить значение этого поля и еще и явно привести его к типу “String” через, например, оператор “as!”.
Но в поле “userRole” - значение “nil” и оно не приведется к типу “String” через конструкцию “as!”, но может привестись без ошибок к типу “String?” через “as?”.
Надеюсь, я прояснил понятно, зачем нужна данная проверка. Продолжим.
На 32 строке мы проверяем, доступно ли нам значение поля “_id”. Чтобы проверить, равно ли возвращаемое значение “nil”, можно было бы использовать такую конструкцию: “aDecoder.decodeObject(..) == nil”, но такая конструкция не слишком информативна.
Поэтому мы используем конструкцию “(aDecoder.decodeObject(..) as? String) == nil”. Далее, если данная конструкция возвращает значение “true”, тогда мы через тернарный оператор присваиваем константе “_id” значение по умолчанию пустая строка, иначе присваиваем реальное значение поля “_id” из настроек телефона.
С 33 по 35 строку мы выполняем те же операции проверок для полей “userName”, “userPwd”, “userDesc”.
На 36 строке мы вызываем параметризированный конструктор и инициализируем его аргументы объявленными ранее константами.
На 47 строке объявлен вспомогательный метод “Populate”, который переносит данные из экземпляра “dictionary” типа “NSDictionary” в поля класса “UserModel”. Этот метод мы будем использовать, когда будем создавать запросы к серверу и получать ответы. В ответе содержится словарь типа “NSDictionary”, в котором находятся свойства с теми же именами, что и поля нашего класса.
С 48 по 51 строку мы инициализируем поля класса “UserModel” значениями из экземпляра “dictionary” c теми же проверками, что мы рассматривали выше.
На 53 строке объявлен вспомогательный метод “PopulateArray”, который переносит данные из экземпляра “array” типа “NSArray” в массив объектов типа “UserModel”. Этот метод мы будем использовать, когда в ответе от сервера придет массив JSON объектов типа “User”, чтобы сконвертировать массив типа “[NSDictionary]” в массив типа “[UserModel]”.
На 55 строке мы объявляем пустой массив типа “[UserModel]” c именем “result”.
На 56 строке мы выполняем цикл “foreach” по элементам массива “array”.
На 58 строке мы создаем новый экземпляр типа “UserModel” с именем “newItem”.
На 59 строке мы проверяем, содержит ли элемент массива значение “nil”.
На 60 строке мы вызываем на экземпляре “newItem” метод “Populate”, который перенесет данные из словаря типа “NSDictionary” в поля экземпляра “newItem”.
На 62 строке мы обращаемся к экземпляру “result” и вызываем у него метод “append”, чтобы добавить новый элемент в массив, и добавляем проинициализированный выше экземпляр “newItem”.
На 64 строке мы возвращаем сконвертированный массив типа “[UserModel]” из метода “PopulateArray”.
Откройте модуль “Models / ProductModel.swift”, заполните его в соответствии с содержимым ниже.
В этом модуле мы создаем модель пользователя, в которую будем десериализовывать JSON объект пользователя, полученный от сервера.
В классе “UserModel” мы добавляем поля модели “_id”, “userName”, “userPwd”, “userDesc”, которые декларировали при объявлении схемы базы данных “Mongo DB” на сервере.
На 9 строке мы подключаем пространство имен “Foundation”.
На 11 строке мы объявляем класс с именем “UserModel”, который наследует класс “NSObject”, являющийся базовым классом для всех классов в iOS платформе. Наследуемся от протокола “NSCoding”, данный протокол необходимо реализовать, чтобы объект можно было сохранять в настройках телефона и извлекать оттуда.
Рассмотрим протокол “NSCoding”.
Данный модуль не нуждается в объяснении, поскольку он по образу и подобию такой же, как и модуль “UserModel.swift”, с разницей лишь в том, что он построен на модели “Product” и содержит соответствующие поля.
Итак, модели данных мы создали, теперь мы сможем с уверенностью сохранять результаты ответа от сервера в нормальном виде и потом использовать их при инициализации элементов управления в соответствующих представлениях :)
Теперь приступаем к реализации классов для отправки запросов на сервер и сохранения/загрузки полученных данных в настройки телефона.
Откройте модуль “Services / Services.swift”, заполните его в соответствии с содержимым ниже.
На 11 строке объявлен класс с именем “Services”. Данный класс будет содержать вспомогательные свойства и методы, которые мы будем часто использовать при работе с ответом от сервера.
На 12 строке объявлено статическое свойство “host” типа “String” и мы ограничили его функциональность только одним аксессором доступа “get”. Т.е. значение свойства получить можно, но изменить нельзя. В данном свойстве содержится адрес, по которому доступен сервер.
В прошлом уроке, когда мы делали сервер, мы использовали адрес “http://localhost:3000” , внутри приложения клиента имеется свой “localhost”, именно поэтому мы используем явный IP адрес “127.0.0.1”, чтобы запрос шел именно на сервер, а не внутрь приложения.
На 18 строке объявлен статический метод “ejectJsonArray”, который принимает аргумент “data” типа “NSData?” и возвращает массив типа “NSArray?”. Данный метод конвертирует экземпляр класса “NSData” в экземпляр класса “NSArray”. В экземпляре “data” будет содержаться массив JSON объектов, полученных от сервера.
На 19 строке объявлена переменная “json” типа “NSArray?” и инициализировано значение по умолчанию “nil”.
На 20 строке мы используем конструкцию “do .. try .. catch” (аналог в C# - “try .. catch”). Символ “_” в блоке “catch” на месте аргумента используется потому, что мы не будем использовать этот аргумент в блоке “catch”.
В случае возникновения исключения мы не будем завершать аварийно приложение, а просто вернем значение “nil” по умолчанию, которое будет говорить о том, что конвертация не удалась, поскольку в NSData не содержится JSON объект.
Аналог JSON объекта в Swift - это класс “NSDictionary”.
На 21 строке мы правым операндом обращаемся к классу “JSONSerialization” и вызываем от его экземпляра статический метод “jsonObject”, который первым аргументом принимает данные типа “Data” для конвертации, вторым аргументом принимает опции конвертации.
Метод “jsonObject” возвращает экземпляр типа “Any”. Далее мы приводим это значение к нужному нам типу “NSArray?”, если приведение было проведено не успешно, будет возвращено значение “nil”.
Результат конвертации присваивается объявленной выше переменной “json”. Обратите внимание на ключевое слово “try” перед конструкцией “JSONSerialization.jsonObject(..)”, использование этого ключевого слова является обязательным, так как метод “jsonObject” имеет в своей сигнатуре ключевое слово “throws”, это означает, что данный метод может генерировать исключения и их необходимо обрабатывать.
На 23 строке в случае исключения вызванного методом “jsonObject” возвращается значение “nil”.
На 25 строке в случае успеха операции возвращается экземпляр типа “NSArray?”.
На 28 строке объвлен метод “ejectJsonDictionary” с той же сигнатурой, что и метод “ejectJsonArray”, но с разницей в возвращаемом значении.
В этом методе возвращается экземпляр типа “NSDictionary?”. Метод подразумевает, что в экземпляре “data” типа “NSData?” содержится единичный JSON объект и пытается его сконвертировать в тип “NSDictionary” , являющийся аналогом JSON объектов в Swift. Реализацию метода смысла описывать не вижу, она такая же, как и в методе “ejectJsonArray”.
Реализуем сохранение моделей данных “UserModel” и “ProductModel” в настройки телефона.
Откройте модуль “Services / UserData.swift”, заполните его в соответствии с содержимым ниже.
Модуль большой, поэтому будем разбирать его, разбив на две части по функциональности.
В первой его части будут описаны методы сохранения / извлечения единичного экземпляра типа “UserModel” и экземпляра типа массив “[UserModel]”.
Во второй его части будут описаны вспомогательные методы для обработчиков ошибок ответа от сервера и сохранения данных в настройки телефона.
Данные методы были созданы для того, чтобы не копипастить один и тот же код много раз, а стандартизировать и сделать его повторно используемым.
На 9 строке подключено пространство имен “UIKit”, необходимое для классов “UserDefaults”, “NSKeyedArchiver”, “NSKeyedUnarchiver”. В пространстве имен уже содержится подключенное пространство имен “Foundation”, поэтому нет смысла его тоже подключать для классов “NSArray”, “NSDictionary”, “NSData”.
На 10 строке подключено пространство имен “Alamofire”, необходимое для класса “DataResponse”.
На 12 строке объявлен класс с именем “UserData”.
На 13 строке объявлен метод “saveUserModel”, который принимает аргумент “userModel” типа “UserModel”. Метод будет сохранять поля модели “UserModel” в настройки телефона.
На 14 строке правым операндом мы обращаемся от экземпляра “UserDefaults” к его статическому свойству “standard”, это свойство является источником данных по отношению к файлу настроек телефона и содержит необходимые методы для сохранения / извлечения данных.
На 15 строке правым операндом мы обращаемся к экземпляру “NSKeyedArchiver” и вызываем статический метод “archivedData”. Этот метод принимает один аргумент “withRootObject” типа “Any” и возвращает экземпляр типа “Data”. Метод упаковывает экземпляр типа “UserModel” в экземпляр типа “Data” (аналог NSData) и возвращает его.
На 16 строке мы от экземпляра “userDefaults” вызываем метод “set”, который принимает два аргумента. Первый аргумент “_” типа “Any?” принимает экземпляр, который необходимо сохранить в настройках телефона. Второй аргумент “forKey” типа “String” необходим, чтобы задать название ключа в словаре настроек телефона. По этому ключу будет произведено сохранение экземпляра “UserModel”.
На 17 строке мы обращаемся к экземпляру “userDefaults”, вызываем от него метод “synchronize”. Данный метод выполняет сохранение экземпляра модели “UserModel” в настройки телефона (аналог в C#, например, 'using(StreamWriter sw = File.CreateText(“test.txt”)) { sw.Write(“some text”); sw.Flush(); }', из этого примера метод “Write” аналогичен методу “set”, а метод “Flush” аналогичен методу “synchronize”).
На 20 строке мы объявили статический метод “getUserModel”, который не принимает аргументов и возвращает экземпляр типа “UserModel”. В этом методе мы излекаем модель “UserModel” из файла настроек телефона по ключу, указанному в предыдущем методе при сохранении этой модели.
На 21 строке мы получаем экземпляр источника данных настроек телефона.
На 22 строке мы проверяем, если модель “UserModel” ранее не сохранялась в настройки телефона, тогда необходимо создать пустую модель по умолчанию.
Вызываем на экземпляре “userDefaults” метод “object”, который принимает аргумент “forKey” типа “String” и возвращает значение типа “Any?”. В качестве аргумента мы передаем ключ, под которым сохраняется данная модель в настройках телефона.
Если значение было найдено в словаре, будет возвращен экземпля , который надо будет привести у нужному типу.
Если значение не было найдено, будет возвращено значение “nil”.
На 23 строке мы создаем пустой экземпляр типа “UserModel”.
На 24 строке мы возвращаем этот экземпляр из метода.
На 26 строке код будет выполнен при наличии записи по ключу в словаре. Правым операндом мы обращаемся к экземпляру “userDefaults”, вызываем метод “object” и передаем в аргумент “forKey” ключ, по которому надо извлечь значение. Полученное значение приводим к типу “NSData”.
На 27 строке мы обращаемся к экземпляру “NSKeyedUnarchiver” и вызываем от него статический метод “unarchiveObject”, который принимает аргумент “with” типа “Data” и возвращает значение типа “Any”. Метод конвертирует экземпляр типа “Data” в экземпляр типа “Any”. Возращаемое значение метода мы явно приводим к типу “UserModel”.
На 28 строке мы возвращаем из метода полученный из словаря экземпляр типа “UserModel”.
На 31 строке объявлен метод “saveListOfUsersModel”, который принимает аргумент “listOfUsersModel” типа “[UserModel]” и ничего не возвращает. Метод сохраняет массив “[UserModel]” в файл настроек телефона. Описывать данный метод не имеет смысла, его работа аналогична методу “saveUserModel”.
На 38 строке объявлен метод “getListOfUsersModel”, который не принимает аргументов и возвращает экземпляр типа массив “[UserModel]”. Метод извлекает из настроек телефона массив типа “[UserModel]” по ключу, указанному в методе “saveListOfUsersModel”. Описывать данный метод не имеет смысла, его работа аналогична методу “getUserModel”.
Откройте модуль “ProductData.swift”, заполните его в соответствии с содержимым ниже.
В первой части описано сохранение / извлечение моделей типа “ProductModel”, “[ProductModel]” в настройки телефона. Описывать код не имеет смысла, его работа точно такая же, как и в модуле “UserData.swift”.
Во второй части описаны вспомогательные методы для обработки запросов от сервера и сохранения данных. Описывать код не имеет смысла, его работа точно такая же, как и в модуле “UserData.swift”.
Теперь, наконец то, мы можем реализовать первый класс для работы с сервисом авторизации пользователя и разобрать работу с библиотекой “Alamofire”.
Откройте модуль “Services / AuthorizationService.swift”, заполните его в соответствии с содержимым ниже.
На 12 строке объявлен класс с именем “AuthorizationService”.
На 13 строке объявлен метод “login”, который принимает три аргумента и ничего не возвращает.
Первый аргумент “userName” типа “String” - это имя пользователя (логин).
Второй аргумент “userPwd” типа “String” - это пароль пользователя.
Третий аргумент - это “callback” метод, который принимает аргумент типа “Bool” и ничего не возвращает.
Метод с такой сигнатурой, переданный в третий аргумент, будет вызван по завершению обработки ответа от сервера. В случае наличия ошибок в аргумент этого метода будет передано значение “false”. В случае успешно проведенной операции - значение “true”.
Да, можно было сделать этот аргумент другим типом, например, отдельным классом ошибок, в который можно было сохранять подробно, что за ошибка произошла.
Но это учебный пример, он итак очень большой. Я решил не усложнять этим жизнь ни себе, ни вам :)
На 14 строке правым операндом мы вызываем метод “request” из библиотеки “Alamofire”.
Метод принимает 5 аргументов и возвращает значение типа “DataRequest”.
Данный метод используется для отправки HTTP/HTTPS запросов на сервер.
Первый аргумент “_” типа “URLConvertible” принимает адрес, по которому необходимо отправить запрос. Мы его инициализируем значением “\(Services.host)/auth”, которое после подстановки свойства “host” будет выглядеть вот так: “http://127.0.0.1:3000/auth”.
Второй аргумент “method” типа “HTTPMethod” принимает HTTP глагол. Тип “HTTPMethod” является перечислением и может принимать следующие значения.
Мы инициализируем данный аргумент значением “.post” (это сокращенная запись, полная запись “HTTPMethod.post”)
Третий аргумент “parameters” типа “Parameters?”. Принимает словарь, ключ - значение, мы его заполняем теми же данными, что отправляли в прошлом уроке через утилиту “Postman”. Конкретно, мы передаем значение имени пользователя и его пароль.
Четвертый аргумент “encoding” типа “ParameterEncoding”. Этим параметром задается заголовок запроса “Content-Type”, т.е. указывается тип данных, который будет отправлен на сервер, сами данные задаются в третьем аргументе “parameters”. Мы инициализируем аргумент значением “JSONEncoding.default”, что аналогично записи “application/json”. Тип “ParameterEncoding” является протоколом и имеет следующие реализации себя в классах “URLEncoding”, “JSONEncoding”, “PropertyListEncoding”. Про эти классы вы можете почитать в официальной документации на сайте “Alamofire”.
Пятый аргумент “headers” типа “HTTPHeaders?” имеет значение по умолчанию “nil”, в нашем вызове метода он не используется. Данный аргумент задается, если вы желаете отправить в заголовках запроса на сервер какую- либо информацию, например, “cookie”. Если у вас сервер написан на Web API и вы используете для авторизации OWin , с настройкой надо использовать “cookie” как ключ авторизации пользователя. Тогда при отправке запросов вам будет необходимо заполнять этот аргумент. Пример его заполнения может выглядеть вот так:
Этот пример взят из моего приложения “Smart Payment”, написанного на Swift 2.2 c минимальной версией iOS 8.3 библиотекой “Alamofire” версии 2. Чтобы этот пример работал на Swift 3, надо заменить значение параметра “encoding” на “JSONEncoding.default”. Этот пример не относится к нашему приложению и его переписывать не нужно.
На 16 строке мы вызываем от экземпляра “r” типа “DataRequest” метод “responseJSON”. Этот метод выполняет запрос на сервер и содержит три аргумента.
Первый аргумент “queue” типа “DispatchQueue?” по умолчанию содержит значение “nil”. Используется при переопределении потока выполнения запроса.
Второй аргумент “options” типа “JSONSerialization.ReadingOptions” по умолчанию содержит значение “.allowFragments”. Используется для задания опция отправки запроса. Тип “ReadingOptions” является структурой и имеет такое объявление.
Третий аргумент “completionHandler” типа делегат “@escaping (DataResponse) -> Void”. Данная сигнатура принимает один аргумент типа “DataResponse” и ничего не возвращает. В этом аргументе содержится ответ от сервера. Сам метод вызывается после запроса, когда сервер отправил ответ на клиент.
Обратите внимание, на 16 строке, когда мы вызываем метод “responseJSON”, у нас нет круглых скобок после метода, а идут сразу фигурные скобки, это называется “closure” подход. В фигурных скобках мы объявляем лямбда метод с сигнатурой, соответствующей третьему аргументу метода “responseJSON”.
Запись “response in” означает следующее: “response” - это аргумент лямбда метода, а само тело метода начинается после ключевого слова “in”. Аргумент “response” имеет тип “DataResponse”.
На 17 строке мы проверяем, если ответ пришел с ошибкой, то это означает, что данных нет и пришло пустое значение типа “nil”. Мы обращаемся к экземпляру “response” и вызываем свойство “result” типа “Result” и от свойства “result” вызываем свойство “value” типа “Value?”. Если в свойстве “value” пришло пустое значение, значит запрос не был выполнен, возможно, по причине недоступности сервера или отсутствия подключения к интернету.
На 18 строке мы вызываем “callback” метод “completion” со значением “false”.
На 21 строке, в случае если проблем с подключением к серверу не возникло, мы правым операндом вызываем от экземпляра “Services” метод “ejectJsonDictionary”, который ранее уже подробно рассматривали. В аргумент “data” мы передаем значение свойства “data”, вызванного от экземпляра “response”. Свойство “data” имеет тип “Data?”. Левому операнду “json” присваивается экземпляр “NSDictionary”, в котором содержится JSON объект пользователя, прошедшего авторизацию, который был отправлен сервером на клиент.
На 22 строке мы проверяем, если пришел JSON объект с ошибкой, тогда возвращаем нужный флаг в “callback” методе “completion”. Помним, что в предыдущем уроке мы создавали JSON объект с ошибкой на сервере, в случае если пользователь ввел неверные данные авторизации, JSON выглядел вот так '{ “Error”: “Authorization error”}'.
У словаря “json” есть возможность получить все ключи, для этого надо вызвать свойство “allKeys”, которое имеет тип “[Any]”. Но, поскольку выполнить поиск строки в массиве объектов нельзя, мы оборачиваем массив объектов в массив типа “Array” и явно приводим его к нужному нам типу массива “[String]”. Мы это делаем, поскольку знаем наверняка, что ключи JSON объекта имеют тип “String”. Затем от полученного экземпляра типа “[String]” мы вызываем метод “contains”, в аргумент которого передает название свойства JSON объекта ошибки, значение “Error”. Если такой JSON объект пришел в ответе, значит пользователь ввел неправильные логин или пароль, или ввел несуществующего пользователя.
На 23 строке мы вызываем “callback” метод “completion” с аргументом “false”.
На 25 строке мы создаем пустой экземпляр “userModel” типа “UserModel”.
На 26 строке мы вызываем на экземпляре “userModel” метод “Populate” в аргумент “dictionary”, передаем экземпляр “json”. Метод “Populate” перенесет данные из экземпляра “json” типа “NSDictionary” в поля экземпляра “userModel”.
На 27 строке мы обращаемся к экземпляру “UserData” и вызываем статический метод “saveUserModel”, чтобы сохранить данные экземпляра “userModel” в настройки телефона. В аргумент “userModel” мы передаем экземпляр “userModel”.
На 28 строке мы вызываем “callback” метод “completion” и передаем в него результат операции “true”.
Позже в контроллере, где мы будем вызывать метод “login”, будет передан “callback” метод “completion”, объявленный в этом контроллере. Тем самым решается проблема ожидания ответа от сервера в контроллере, так как есть “callback” метод. Внутри этого метода мы проверим результат операции и затем обратимся к экземпляру “UserData” и вызовем метод “getUserModel”, который вернет нам экземпляр “UserModel”, с которым мы сможем работать на стороне контроллера.
Откройте модуль “Services / UserService.swift”, заполните его, как показано ниже.
На 12 строке объявлен класс “UserService”.
На 13 строке объявлен метод “get”, который принимает два аргумента и ничего не возвращает.
Первый аргумент “id” типа “String?” принимает идентификатор пользователя “_id”. Если передать в аргумент значение “nil”, будет возвращен весь список пользователей, если передать конкретный идентификатор, будет возвращен конкретный пользователь.
Второй аргумент “completion” типа делегат “(Bool) -> Void” используется для получения сигнала, что ответ был получен и основные операции были выполнены.
На 29 строке объявлен метод “create”, который принимает 4 аргумента и ничего не возвращает. Метод отправляет запрос на создание пользователя на сервер.
Первые три аргумента “userName”, “userPwd”, “userDesc” имеют тип “String” и содержат данные пользователя. Идентификатор пользователя в операции создания не нужен. Так как идентификатор пользователю присваивается на стороне сервера и данный метод выполняет как раз эту операцию.
Четвертый аргумент “completion” типа делегат “(Bool) -> Void” используется для получения сигнала, что ответ был получен и основные операции были выполнены.
На 37 строке объявлен метод “update”, который принимает 5 аргументов и ничего не возвращает. Данный метод отправляет запрос на обновление данных пользователя на сервер.
Первые 4 аргумента “id”, “userName”, “userPwd”, “userDesc” имеют тип “String” и содержат данные пользователя.
Пятый аргумент “completion” типа делегат “(Bool) -> Void” используется для получения сигнала, что ответ был получен и основные операции были выполнены.
На 45 строке мы объявляем метод “delete”, который принимает два аргумента. Этот метод отправляет запрос на удаление пользователя на сервер.
Первый аргумент “id” типа “String?” принимает идентификатор пользователя “_id”.
Второй аргумент “completion” типа делегат “(Bool) -> Void”, используется для получения сигнала, что ответ был получен и основные операции были выполнены.
Откройте модуль “Services / ProductService.swift”, заполните его, как показано ниже
На 12 строке объявлен класс “ProductService”.
На 13 строке объявлен метод “get”, который принимает два аргумента и ничего не возвращает.
Первый аргумент “id” типа “String?” принимает идентификатор товара “_id”. Если передать в аргумент значение “nil”, будет возвращен весь список товаров, если передать конкретный идентификатор, будет возвращен конкретный товар.
Второй аргумент “completion” типа делегат “(Bool) -> Void” используется для получения сигнала, что ответ был получен и основные операции были выполнены.
На 29 строке объявлен метод “create”, который принимает 4 аргумента и ничего не возвращает. Метод отправляет запрос на создание товара на сервер.
Первые три аргумента “productImage”, “productName”, “productDescription” имеют тип “String” и содержат данные товара. Идентификатор товара в операции создания не нужен. Так как идентификатор товару присваивается на стороне сервера и данный метод выполняет как раз эту операцию.
Четвертый аргумент “completion” типа делегат “(Bool) -> Void” используется для получения сигнала, что ответ был получен и основные операции были выполнены.
На 37 строке объявлен метод “update”, который принимает 5 аргументов и ничего не возвращает. Данный метод отправляет запрос на обновление данных товара на сервер.
Первые 4 аргумента “id”, “productImage”, “productName”, “productDescription” имеют тип “String” и содержат данные пользователя.
Пятый аргумент “completion” типа делегат “(Bool) -> Void” используется для получения сигнала, что ответ был получен и основные операции были выполнены.
На 45 строке мы объявляем метод “delete”, который принимает два аргумента. Этот метод отправляет запрос на удаление товара на сервер.
Первый аргумент “id” типа “String?” принимает идентификатор товара “_id”.
Второй аргумент “completion” типа делегат “(Bool) -> Void” используется для получения сигнала, что ответ был получен и основные операции были выполнены.
Заметьте, адреса запросов, используемых нами при отправке на сервер, совпадают с теми, что мы использовали в прошлом уроке в утилите “Postman”.
На этом урок завершен.
На следующем уроке мы продолжим разработку клиента, создадим контроллеры и представления для редактирования пользователей, подключим работу сервисов к странице входа в систему, создадим форму регистрации пользователя.
Введення в розробку програм під iOS. Частина 4
Автор: Volodymyr Bozhek
Здравствуйте, дорогие читатели.
В этом уроке мы научимся:
скрывать клавиатуру мобильного устройства для полей “Text Field”, “Text View”;
добавлять различные типы клавиатур цифровая, e-mail, URL;
добавлять панель инструментов на клавиатуру мобильного устройства;
добавим панель поиска в список товаров;
добавим предварительный просмотр на ячейки списка товаров с использованием аппаратной технологии 3D Touch.
Откройте проект Warehouse.
В панели навигатора откройте модуль “ViewController.swift”, нам необходимо унаследоваться от протокола “UITextFieldDelegate”:
Теперь реализуем этот протокол:
Разберем код построчно.
На 43 строке мы реализовали метод “textFieldShouldReturn” протокола “UITextFieldDelegate”. Метод вызывается, когда вы на клавиатуре мобильного устройства нажимаете кнопку “Return” (Done, Next).
Метод принимает один аргумент, который содержит экземпляр текстового поля, для которого была вызвана клавиатура. Возвращаемое значение данного метода говорит о том, скрывать клавиатуру или нет. Мы возвращаем значение “true”, означающее, что мы хотим скрывать клавиатуру.
С 44 по 51 строку идет поиск активного в данный момент текстового поля, куда пользователь установил курсор. Данный код мы добавили, чтобы сделать следующую функциональность. Пользователь ставит курсор в поле “Имя пользователя”, вводит имя пользователя. Затем на клавиатуре нажимает кнопку “Next”, курсор автоматически попадает на поле пароль. После того, как пользователь ввел пароль, он нажимает кнопку “Done” и клавиатура пропадает.
Чтобы поставить курсор в текстовое поле, необходимо на экземпляре этого текстового поля вызвать метод “becomeFirstResponder”.
Чтобы убрать курсор с текстового поля, необходимо на экземпляре текстового поля вызвать метод “resignFirstResponder”.
На 55 строке мы переопредили метод “touchesBegan”. Данный метод объявлен в классе “UIResponder”, класс “UIViewController” наследуется от этого класса.
Метод “touchesBegan” вызывается, когда вы нажимаете в любую свободную область экрана, находящуюся вне редактируемого текстового поля.
На 56 строке мы обращаемся к внутреннему свойству “view”, которое содержит экземпляр представления, подвязанного к контроллеру. И на нем вызываем метод “endEditing” со значение аргумента “true”.
Данный метод посылает представлению событие о том, что необходимо завершить редактирование элементов управления, которые на нем расположены.
Но это еще не все. Теперь необходимо сообщить текстовым полям, что мы реализовали для них соответствующий протокол “UITextFieldDelegate” и хотим проинициализировать у них свойство “delegate”.
Обновите код в методе “viewDidLoad”:
На 30 и 31 строке мы проинициализировали свойство “delegate” для текстовых полей “Имя пользователя” и “Пароль пользователя”. Теперь эти поля знают, откуда им надо вызывать реализацию метода “textFieldShouldReturn”.
Откройте модуль “Main.storyboard”, найдите и выделите представление “View Controller”. На этом представлении выделите текстовое поле “Имя пользователя”. В панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Return key” и установите в него значение “Next”.
Затем выделите текстовое поле “Пароль пользователя” и установите для его свойства “Return key” значение “Done”.
Запустите приложение.
Если клавиатура у вас не открылась в симуляторе, выполните в меню симулятора “Hardware -> Keyboard -> Toggle Software Keyboard”.
Теперь давайте разберем, какие типы клавиатур имеются и в каком свойстве они задаются. Потренируемся пока на текстовом поле “Имя пользователя”. Выделите это текстовое поле в дизайнере и в панели свойств на вкладе “Show the Attributes inspector” найдите свойство “Keyboard Type”, в этом свойстве задается тип вызываемой клавиатуры для текстового поля.
Часто используемые виды клавиатур:
Default – клавиатура по умолчанию, содержит в себе все раскладки.
Numbers and punctuation – клавиатура содержит цифры и знаки пунктуации.
URL – клавиатура содержит все необходимые символы для быстрого ввода URL адреса.
Number Pad – клавиатура содержит только цифры, без знаков разделения на дробные составляющие.
Phone Pad – телефонная клавиатура.
E-mail Address – клавиатура содержит необходимые символы для быстрого ввода e-mail адреса.
Decimal Pad – клавиатура содержит цифры и знаки разделения на дробные составляющие.
Теперь добавим функциональность закрытия клавиатуры для представления редактирования продукта. Откройте модуль “ProductViewController.swift”. Добавьте наследование от двух протоколов “UITextFieldDelegate”, “UITextViewDelegate”, для класса “ProductViewController”:
Реализуем эти протоколы:
Методы на 58 строке и 69 мы рассматривали уже ранее, не буду повторяться.
На 64 строке мы реализовали метод “textViewDidEndEditing”, данный метод вызывается, когда редактирование многострочного текстового поля было завершено.
Обновим код в методе “viewDidLoad”:
Запустите приложение. Закрытие клавиатуры будет работать только для текстового поля “Название”, для описания работать не будет.
Дело в том что, чтобы скрыть клавиатуру у поля “Text View”, надо добавить дополнительную панель инструментов над клавиатурой и на нее добавить кнопку, которая будет закрывать эту клавиатуру.
Разберем, как это сделать. Обновите код:
Разберем код построчно.
На 37 строке мы вызываем метод “addToolbar”, который добавим ниже. Данный метод принимает аргумент типа “UITextView”, в него мы передаем экземпляр текстового поля, к клавиатуре которого будет привязана панель инструментов с кнопкой закрыть.
На 40 строке мы объявили метод “addToolbar”.
На 41 строке мы создали экземпляр панели инструментов. Чтобы добавить эту панель на клавиатуру, надо проинициализировать свойство “inputAccessoryView” созданной панелью инструментов.
С 42 по 44 строку мы задаем визуальные стили панели инструментов.
На 45 строке мы создаем экземпляр кнопки пробел.
На 46 строке мы создаем экземпляр кнопки “Х Закрыть” и привязываем к нему метод обработчик “donePressed” на нажатие по кнопке.
На 47 строке мы добавляем кнопки на панель инструментов.
На 48 строке говорим, что панель инструментов будет принимать действия пользователя.
На 49 строке мы вызываем метод “sizeToFit”, который растянет панель инструментов по ширине экрана.
На 51 строке инициализируем свойство “delegate” текстового поля.
На 52 строке добавляем панель инструментов на клавиатуру.
Запустите приложение.
Теперь, при нажатии на кнопку “Х Закрыть” на панели инструментов клавиатуры, клавиатура закрывается. Данный код еще удобно использовать для цифровых клавиатур.
Добавим поле поиска товаров в представление списка товаров. Откройте модуль “SuppliesViewController.swift”.
Добавьте наследование от протоколов “UISearchResultsUpdating” и “UISearchBarDelegate”. Обновите код:
Разберем код.
На 27 строке мы добавили свойство “filteredSupplies”, в котором будут содержаться найденные товары через поле поиска.
На 28 строке мы добавили свойство, которое указывает, активно ли сейчас поле поиска или нет.
На 29 строке мы объявили пока еще не инициализированный экземпляр контроллера для поля поиска типа “UISearchController”.
Отлично, теперь обновим методы табличного представления “numberOfRowsInSection” и “cellForRowAt indexPath”:
Разберем код.
На 74 строке мы проверяем, является ли поле поиска в данный момент активным (т.е. мы в данный момент поставили в него курсор).
Тогда на 75 строке мы возвращаем количество товаров из отфильтрованной коллекции.
Иначе на 78 строке мы возвращаем исходный список товаров.
На 84 строке мы изменили получение модели товара. Теперь мы проверяем: если поле поиска в данный момент активно, то возвращаем товар из отфильтрованной коллекции, иначе возвращаем товар из исходной коллекции.
Ошибки с индексом за пределами коллекции у нас не будет, так как выше мы обновили реализацию метода “numberOfRowsInSection”.
Теперь необходимо добавить метод, который будет инициализировать контроллер для поля поиска:
Разберем код этого метода.
На 131 строке мы инициализируем свойство “searchController”, которое объявляли ранее.
На 132 строке мы указываем ссылку на представление, где находятся данные, которые мы будет фильтровать.
На 133 строке мы указываем, доступен ли нам основной контент в ходе поиска данных.
На 134 строке мы задаем текст, который будет отображаться в поле поиска.
На 135 строке мы инициализируем свойство “delegate” поля поиска, тем самым говоря, откуда ему брать реализацию связанных с ним протоколов. Мы указали, что реализация находится в контроллере “SuppliesViewController”.
На 136 строке мы вызываем метод “sizeToFit” на поле поиска, чтобы растянуть панель поиска по ширине экрана.
На 137 строке левым операндом мы обращаемся к свойству “tableHeaderView” экземпляра таблицы и правым операндом инициализируем его нашим полем поиска. Тем самым мы добавляем в заголовок нашей таблицы поле поиска.
Наше приложение на русском языке. А когда поле поиска становится активным, мы видим справа от него кнопку с текстом на английском языке “Cancel”, давайте сразу это исправим, чтобы потом не возращаться к этому:
Разберем построчно код.
На 141 строке мы обращаемся к свойству “searchController”, от него вызываем свойство “searchBar” - это наше поле поиска. И на экземпляре поле поиска вызываем свойство “showsCancelButton”, говорящее о том, отображать кнопку “Cancel” справа от поля поиска или не отображать. Мы говорим, что будем отображать.
На 142 строке мы объявляем переменную типа “UIButton” с именем “cancelButton”.
На 143 строке мы получаем представление, в котором будем производить ниже поиск кнопки с текстом “Cancel” по типу разумеется :)
На 144 строке мы проходимся по иерархии вложенных представлений в поле поиска.
На 145 строке мы проверяем: если представление имеет тип “UINavigationButton”, то это то представление, которое нам надо, это и есть наша кнопка с текстом “Cancel”.
На 146 строке мы инициализируем переменную “cancelButton” экземпляром найденной кнопки.
На 147 строке мы задаем текст кнопки “Отмена”.
Реализуем протокол “UISearchResultsUpdating”:
Разберем код.
На 154 строке мы вызываем метод “searchDisplayControllerWillBeganSearch”, который ранее рассматривали уже, и передаем в аргумент “controller” экземпляр контроллера поиска, который у нас есть в аргументе метода “updateSearchResults”.
На 155 строке мы проверяем, ввел ли пользователь какой -либо текст в поле поиска, если ввел, тогда сохраняем этот текст в переменную “searchString”, если не ввел, тогда выходим из метода “updateSearchResults”.
На 159 строке мы выполняем фильтрацию данных из основного источника данных по тексту, который ввел пользователь в поле поиска. Для этого у коллекции “supplies” вызываем метод “Filter”, в который передаем лямбда метод с одним аргументом типа “ProductModel”, и возвращаемым значением типа Bool (найден или не найден товар).
На 160 строке мы создаем переменную с именем “product” типа “ProductModel” и инициализируем ее экземпляром аргумента “productItem”. Заметьте, лямбда метод будет вызываться столько раз, сколько элементов товара содержит коллекция “supplies”.
На 161 строке мы объявляем переменную, в которой будет содержаться результат сравнения текста поиска с названием или описанием нашего товара.
На 162 строке мы выполняем сравнение текста текущего товара в лямбда методе с текстом поля поиска . Оба текста путем вызова метода “lowercased” переведены в нижний регистр и теперь регистро независимы. Если по названию товара совпадений не было найдено, пытаемся сравнить по описанию товара. Результат сравнения записываем в переменную результата “res”.
На 164 строке возвращаем результат метода “filter”.
На 166 строке запускаем переинициализацию таблицы для отображения отфильтрованных данных и скрытия данных, не попавших под фильтр.
Реализуем протокол “UISearchBarDelegate”:
Разберем код.
На 170 строке мы реализовали метод “searchBarTextDidBeginEditing”, который вызывается, когда пользователь начинает вводить текст в поле поиска. В реализации данного метода мы задаем свойство “shouldShowSearchResults” в значение “true”, тем самым давая понять контроллеру поиска, что поле поиска сейчас активно. Затем мы переинициализируем таблицу.
На 175 строке мы реализовали метод “searchBarCancelButtonClicked”, который вызывается, когда пользователь нажал кнопку “Cancel” или в нашем случае “Отмена”, справа от поля поиска. В реализации мы говорим, что поле поиска теперь больше не активно и переинициализируем таблицу.
На 180 строке мы реализовали метод “searchBarSearchButtonClicked”, который вызывается, когда на клавиатуре, отобразившейся к полю поиска, мы нажали кнопку “Search” (“Поиск”).
В реализации метода мы проверяем: если свойство “shouldShowSearchResults” еще не активно, активируем его и переинициализируем таблицу. Затем скрываем клавиатуру для поля поиска на 185 строке.
Обновите метод “viewDidLoad”:
На 64 строке мы добавили заголовок представления “Продукты”.
На 66 строке мы вызвали метод, который выполняет инициализацию контроллера поиска и добавляет его в наше представление.
Запустите приложение.
Нажмите на поле “Поиск товара”, введите туда, например, букву “Ш”.
Все работает :)
Теперь в этом уроке нам еще осталось добавить предварительный просмотр товара при выполнении на нем функции 3D Touch. Данная функция поддерживается только на устройствах iPhone 6s / 6s Plus / 7 / 7 Plus. В этих телефонах установлен специальный дисплей, который реагирует на силу нажатия на него. Даже если у вас нет подобного девайса это не проблема, так как данную функциональность можно проверить в симуляторе.
Реализуем протокол “UIViewControllerPreviewingDelegate”. Обновите код “SuppliesViewController”:
Теперь обновите метод “viewDidLoad”:
На 67 строке мы обращаемся к свойству контроллера “traitCollection”, в котором содержится свойство “forceTouchCapability”, и проверяем: если данное устройство поддерживает данную функцию (3D Touch), тогда вызываем метод “registerForPreviewing”, в который первым аргументом передаем ссылку на контроллер, для которого надо применить данную функциональность, и экземпляр таблицы, на содержимом которой эта функциональность и будет выполняться.
Добавим реализацию протокола “UIViewControllerPreviewingDelegate”:
Разберем код.
На 192 строке мы реализовали метод “previewingContext”, который возвращает экземпляр представления , которое надо отобразить в предварительном просмотре. Метод принимает два аргумента.
Первый аргумент “previewContext” типа “UIViewControllerPreviewing” принимает экземпляр контроллера предварительного просмотра.
Второй аргумент “location” типа “CGPoint” содержит позицию места, где пользователь выполнил усиленное нажатие по ячейке таблицы.
На 193 строке мы получаем экземпляр “indexPath” путем вызова на таблице метода “indexPathForRow”, аргументом которого мы передаем аргумент “location”.
Данный экземпляр нам необходим, поскольку у него имеется свойство “row” и по значению этого свойства мы сможем получить нужную модель товара.
На 194 строке мы производим поиск контроллера ”ProductViewController” по его “Storyboard ID”. Это представление редактирования товара.
На 195 строке мы передаем идентификатор товара в свойство “id” найденного выше контроллера.
На 196 строке мы передаем в свойство “sourceRect” полную область ячейки, по которой было выполнено “3D Touch”. Благодаря этому данная область будет выделена, когда вы выполните нажатие “3D Touch” в таблице.
На 197 строке возвращаем найденный контроллер “ProductViewController”.
На 199 строке возвращается пустое значение, если пользователь выполнил нажатие “3D Touch” вне области таблицы.
На 202 строке мы реализовали метод “previewingContext” перегруженный, который ничего не возвращает и отображает представление того контроллера, который мы вернули в методе выше.
Запустите приложение.
На этом завершим урок.
На следующем уроке мы рассмотрим создание иконки нашего приложения и разберемся, как пользоваться “Assets”.
Введення в розробку програм під iOS. Частина 3
Автор: Volodymyr Bozhek
Здравствуйте, дорогие читатели.
В этом уроке мы:
создадим форму редактирования товара;
рассмотрим понятие делегата и удобство его использования;
добавим действия редактирования / удаления товара в списке товаров.
Откройте проект Warehouse.
В первую очередь, давайте изменим цвет View входа в систему с “Light Green” на “White”, я явно перестарался с выбором цвета в самом начале :).
В будущих уроках, когда разберем, как пользоваться Assets, мы заменим фон на красивую картинку, подходящую по смыслу к этому приложению, а пока сделаем нейтральный цвет, не бросающийся в глаза.
Для этого в панели навигатора откройте модуль “Main.storyboard”, выделите “View Controller”, затем в панели свойств откройте вкладку “Show the Attributes inspector”.
Найдите свойство “Background” и установите значение “White”.
Теперь найдите в библиотеке компонентов компонент “Table View Controller” и перетащите его в свободную область дизайнера рядом с “Supplies View Controller”, созданным в прошлом уроке.
Выделите только что добавленный компонент. Найдите кнопку в дизайнере “Show Document Outline”, нажмите на нее, откроется панель документов.
На этой панели разверните компонент “Table View Controller” и выделите компонент “Table View”. Затем в панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Content”.
По умолчанию у этого свойства установлено значение “Dynamic Prototypes”, установите значение “Static Cells”, в дизайнере вы увидите, что интерфейс контроллера изменился и отобразилось 3 строки. Разберем значения этого свойства.
Значение “Dynamic Prototypes” используется, если вам необходимо иметь динамический набор данных, который будет постоянно изменяться. Установив это значение, обязательно необходимо переопределить методы протокола “UITableViewDataSource” для корректной работы контроллера.
Значение “Static Cells” используется, если вам необходимо иметь статические данные, которые не будут меняться. Установив это значение, вам не нужно переопределять методы протоколы “UITableViewDataSource”.
В данном примере мы устанавливаем это значение с целью удобства размещения элементов управления на нашей View. У нас будут ячейки с таким содержимым: ячейка под картинку, идентификатор товара, название товара, описание товара.
Сейчас у нас 3 ячейки, надо чтобы было 4. В первой ячейке будет компонент “Image View”, в остальных 2 ячейках будут элементы управления “Label” и “Text Field”, в последней 4-й ячейке будут элементы управления “Label” и “Text View”.
На панели документов для “Table View Controller” выделите компонент “Table View Section”. В панели свойств откройте вкладку “Show the Attributes inspector”. Найдите свойство “Rows”. Сейчас у этого свойство установлено значение “3”. Измените значение на “4”, у вас появится 4 ячейка.
В панели документов раскройте компонент “Table View Sections”, вы увидите список ячеек типа “Table View Cell”. Выделите первую ячейку. В панели свойств откройте вкладку “Show the Size inspector”. На этой вкладке можно управлять размерами компонента. Найдите свойство “Custom” и отметьте его галочкой. Затем найдите свойство “Row Height”. Установите значение этого свойства “144”, высота ячейки изменится.
Перетащите на эту ячейку из библиотеки компонентов компонент “Image View” и растяните его на всю область ячейки.
Выделите компонент “Image View” и нажмите кнопку “Pin”. В открывшемся диалоговом окне снимите галочку с поля “Constrain to margin” и отметьте позиционирование относительно всех стенок ячейки.
Нажмите кнопку “Add 4 Constraints”.
В панели документов выделите вторую ячейку. Перетащите из библиотеки компонентов компонент “Label” на эту ячейку. Поместите его с левого края ячейки и растяните по высоте ячейки.
В панели свойств откройте вкладку “Show the Size inspector”, установите свойство “Width” в значение “120”. Откройте вкладку “Show the Attributes inspector”, установите безымянное свойство с текстом “Label” на текст “Идентификатор”.
Найдите свойство “Font”, нажмите на кнопку со значком “T”, откроется диалоговое окно задания свойств шрифта. Установите свойство “Style” в значение “Bold”, свойство “Size” в значение “12”. Нажмите кнопку “Done”.
Представление должно выглядеть так:
Из библиотеки компонентов перетащите компонент “Text Field” и расположите рядом с компонентом “Label”. Растяните компонент “Text Field” на оставшуюся ширину ячейки.
Выделите элемент управления “Label”, нажмите кнопку “Pin”. Снимите галочку с поля “Constrain to margin”, отметьте позиционирование относительно верхней и левой стенок ячейки. Отметьте галочкой поле “Height”.
Нажмите кнопку “Add 3 Constraints”. Выделите элемент управления “Text Field”, нажмите кнопку “Pin”. В диалоговом окне снимите галочку с поля “Constrain to margin”, установите позиционирование относительно левой, верхней, правой стенок ячейки, отметьте галочкой поле “Height”.
Нажмите кнопку “Add 4 Constraints”.
В панели документов выделите 3 ячейку. Повторите для нее все операции, которые были применены ко второй ячейке. Измените текст элемента управления “Label” на “Название”. Должно получиться вот так:
В панели документов выделите последнюю ячейку, на вкладе “Show the Size inspectors” установите в поле “Row Height” значение “144”, предварительно отметив поле “Custom” галочкой. Перенесите на ячейку два компонента “Label” и “Text View”. В Установите ограничения для элемента управления “Label”, как мы устанавливали для предыдущих элементов управления “Label”, а для элемента управления “Text View” такие же ограничения, как для “Text Field”.
Для элемента управления “Label” установите текст “Описание”, для элемента управления “Text View” уберите текст по умолчанию. Должно получиться вот так:
В панели навигатора, добавьте новый модуль “Swift File” с именем “ProductViewController.swift”. Измените код, как показано ниже:
Перейдите снова в дизайнер , выделите “Table View Controller”, с которым проводили изменения, в панели свойств, откройте вкладку “Show the Identity inspector”. Установите свойство “Class” значением “ProductViewController”, свойство “Storyboard ID” значением “ProductViewController”.
В панели инструментов нажмите кнопку “Show the Assistant editor”.
Для элемента управления “Image View” создайте переменную с именем “imageView”.
Во второй ячейке, для элемента управления “Text Field”, создайте переменную с именем “idTxt”.
В третьей ячейке, для элемента управления “Text Field”, создайте переменную с именем “nameTxt”.
В четвертой ячейке, для элемента управления “Text View”, создайте переменную с именем “descriptionTxt”. Должно получиться вот так:
В панели навигатора, добавьте новый модуль “Swift File” с именем “ProductModel.swift”. Добавьте в него следующий код:
Разберем добавленный код построчно.
На 9 строке мы подключили пространство имен “Foundation” для класса “NSObject”.
На 11 строке мы объявили класс с именем “ProductModel”, который наследуется от класса “NSObject” для того, чтобы считаться типом данных.
На 12 строке мы объявили переменную с именем “productId” типа “Int” и инициализировали ее значение по умолчанию “-1”.
На 13 строке мы объявили поле с именем “productImage” типа “String” и инициализировали ее значение по умолчанию “”.
На 14 строке мы объявили поле с именем “productName” типа “String” и инициализировали ее значение по умолчанию “”.
На 15 строке мы объявили поле с именем “productDescription” типа “String” и инициализировали ее значение по умолчанию “”.
На 17 строке мы объявили параметризированный инициализатор класса “ProductModel” (это тоже самое, что и конструктор в языке программирования C#).
Инициализатор - это метод, который вызывается при создании нового экземпляра класса. Данный инициализатор принимает аргументы с такими же именами, как имена полей в классе.
Для того, чтобы инициализировать внутренние поля класса значениями полей аргументов. Мы левым операндом обращаемся к самому себе, через ключевое слово “self” и обращаемся к нужному нам полю, а правым операндом задаем значение аргумента.
Мы плавно подходим к созданию делегата. В панели навигатора добавьте новый модуль “Swift File” с именем “ProductProtocol.swift”. Добавьте в него следующий код:
Разберем добавленный код.
На 11 строке мы объявили протокол с именем “ProductProtocol”. Протокол - это тоже самое, что и интерфейс в языке программирования C#. Экземпляр протокола через инициализатор создать нельзя, поскольку это только прототип, который не содержит реализации и используется в качестве описания и задания соглашений, которые разработчик должен реализовать сам в будущем.
На 12 строке мы объявили метод прототип с именем “changeProduct”, который принимает аргумент типа “ProductModel” с именем “model”.
Откройте модуль “ProductViewController.swift”, добавьте поле с именем “delegate” типа “ProductProtocol?” и поле с именем “id” типа “Int” со значением по умолчанию “-1”. Должно получиться вот так:
Добавьте после метода “viewDidLoad”, новые методы с именами “initNavigationButtons”, “backToParent”, “saveData”, которые ничего не принимают и ничего не возвращают. Код:
Разберем построчно добавленный код.
На 21 строке мы вызываем метод “initNavigationButtons”, когда View находится в процессе загрузки, чтобы инициализировать кнопки навигации.
На 24 строке мы объявили метод с именем “initNavigationButtons”.
На 25 строке мы переопределяем левую кнопку навигации, задаем ей текст “Отмена” и обработчик события на нажатие, метод “backToParent”.
На 26 строке мы создаем кнопку с именем “saveButton” и текстом “Сохранить”, добавляем для нее обработчик событие на нажатие, метод “saveData”.
На 27 строке мы обращаемся к экземпляру панели навигации контроллера “navigationItem” и вызываем от него метод “setRightBarButton”, который принимает два аргумента. Первый аргумент - это экземпляр добавляемой на панель навигации кнопки, второй аргумент - это стиль добавления кнопки, добавить с анимацией или без.
Сам метод, как видно из названия, добавляет кнопку справа на панель навигации. Это вариант, как добавить правую кнопку из кода.
На 30 строке мы объявили метод с именем “saveData”.
На 31 строке мы обращаемся к нашему делегату с именем “delegate” и проверяем, если он инициализирован, тогда вызываем у него метод “changeData” и передаем туда модель данных, заполненную данными собранными с элементов управления.
На 32 строке мы создаем экземпляр типа “ProductModel” с именем “model”.
На 33 строке мы вызываем у делегата метод “changeData” и передаем туда экземпляр “model”. Данный метод будет реализован в классе “SuppliesViewController”.
На 35 строке мы возвращаемся на предыдущую View (SuppliesViewController) в стеке иерархий вызовов View.
На 38 строке мы объявляем метод с именем “backToParent”. Данный метод делает переход на предыдущую View (SuppliesViewController) в стеке иерархий вызовов View.
Теперь откройте модуль “SuppliesViewController.swift”. Нам необходимо обновить его реализацию:
Разберем обновленный код построчно.
На 12 строке в классе SuppliesViewController мы наследуемся от протокола “ProductProtocol”.
На 13 строке мы заменили тип данных для поля “supplies”. Тип данных был “[String]”, стал “[ProductModel]”. И сделали это поле статическим для того, чтобы была возможность доступа к нему из контроллера “ProductViewController”.
На 29 строке мы реализовываем метод “changeProduct” протокола “ProductProtocol”. Именно этот метод будет вызываться при нажатии кнопки “Сохранить” в контроллере “ProductViewController”.
На 30 строке мы проверяем идентификатор продукта, если “-1” - это означает, что это новый продукт. Иначе существующий продукт, и мы его обновляем.
На 32 строке мы правым операндом генерируем идентификатор продукта путем вызова метода “makeNewProductId”. Данный метод возвращает значение типа “Int”, которое мы присваем левому операнду. Левый операнд - это поле “productId” экземпляра “ProductModel”.
На 33 строке мы добавляем новый продукт в коллекцию продуктов.
На 36 строке мы объявляем переменную с именем “changedIndex” типа “Int” со значением по умолчанию “-1”. В эту переменную мы запишем найденную ниже в цикле позицию продукта в коллекции “supplies”.
На 37 строке мы в цикле проходимся по элементам коллекции “supplies” и ищем продукт, который нужно обновить.
На 38 строке описано условие поиска продукта.
На 39 строке, когда продукт был найден, мы сохраняем его позицию в переменную “changedIndex”.
На 40 строке выходим из цикла.
На 43 строке проверяем, если продукт был найден , тогда обновляем этот продукт.
С 44 по 46 строку мы обновляем свойства продукта , значениями свойств модели, переданной аргументом метода “changeProduct”.
На 49 строке мы запускаем переинициализацию “DataSource” для табличного представления, чтобы данные в ячейках обновились и добавились/удались обновленные данные.
На 52 строке мы объявили метод с именем “makeNewProductId”, который ничего не принимает и возвращает значение типа “Int”.
На 53 строке левым операндом мы объявили переменную с именем “sortProducts” типа “[ProductModel]”. Правым операндом мы на экземпляре коллекции “supplies” вызываем метод “sorted”, который принимает лямбда метод сортировки коллекции объектов и возвращает отсортированную коллекцию.
У лямбда метода есть два аргумента, в них находятся экземпляры сравниваемых объектов из коллекции “suppliers”. Данный метод возвращает значение типа “Bool”.
На 54 строке мы возвращаем условие сортировки по возрастанию. Сортировка производится по свойству “productId” класса “ProductModel”.
На 56 строке мы обращаемся к последнему элементу отсортированной коллекции, тем самым получаем максимальный идентификатор продукта в коллекции и увеличиваем его на единицу.
Теперь надо обновить реализацию метода “cellForRowAt indexPath”:
Разберем код построчно.
На 69 строке мы изменили вид обращения к коллекции “supplies”.
На 73 строке мы изменили для экземпляра ячейки тип стиля. Был “.plain”, стал “subtitle”.
На 74 строке мы левым операндом объявили константу с именем “model” типа “ProductModel”. Правым операндом мы инициализируем константу значением из коллекции “supplies”.
На 75 строке мы записываем в свойство “tag” идентификатор продукта для этой ячейки.
На 76 строке в элемент управления “Label”, который находится в верхней области ячейки, мы записываем название продукта.
На 77 строке в элемент управления “Label”, который находится в нижней области ячейки, мы записываем описание продукта.
На 78 строке мы активируем в ячейке информационную кнопку справа. При нажатии на эту кнопку мы будем редактировать продукт.
Теперь нам надо подвязаться на событие нажатия на информационную кнопку в ячейке.
Для этого необходимо переопределить метод протокола “UITableViewDelegate” ,реализованного в классе “UITableViewController” с именем “accessoryButtonTappedForRowWith”.
Разберем код построчно.
На 83 строке мы получаем экземпляр типа “ProductModel”, привязанный к ячейке, и сохраняем его в константу с именем “model”.
На 84 строке мы производим поиск контроллера с именем “ProductViewController” в модуле “Main.storyboard”.
На 85 строке на найденном экземпляре контроллера обращаемся к свойству “delegate” типа “ProductProtocol” и инициализируем его ссылкой на самого себя, тем самым говоря, что в этом классе уже реализован метод этого протокола с именем “changeProduct” и вызывать метод надо именно из этого класса.
На 86 строке мы на найденном экземпляре контроллера обращаемся к свойству “id” и инициализируем его идентификатором продукта, привязанного к текущей ячейке.
На 87 строке мы открываем контроллер “ProductViewController”.
Редактирование мы почти сделали, осталось добавить инициализацию редактируемых данных в контроллере “ProductViewController”.
Давайте реализуем еще в этом контроллере удаление данных. Для этого надо переопределить два метода объявленных в протоколе “UITableViewDelegate” и реализованных в классе “UITableViewController”. Эти методы называются “canEditRowAt IndexPath” и “commit editingStyle”. Обновите свой код в соответствии с представленным ниже:
Разберем код построчно.
На 90 строке мы переопределили метод “canEditRowAt IndexPath”, данный метод возвращает значение , которое говорит о том, активировать удаление строк или нет.
На 91 строке возвращаем значение активировать.
На 94 строке мы переопределили метод “commit editingStyle”, данный метод вызывается, когда мы нажали кнопку удалить на ячейке.
На 95 строке мы удаляем продукт из коллекции “supplies”.
На 96 строке обновляем содержимое таблицы.
Теперь добавим кнопку добавления нового товара. Для этого обновим код метода “initNavigationButtons”.
Разберем код построчно.
На 100 строке мы создали экземпляр кнопки с текстом “Новый продукт” и обработчиком события на нажатие - метод “newProduct”.
На 101 строке мы добавили выше созданную кнопку в панель навигации контроллера в область справа.
На 104 строке мы объявили метод “newProduct”, которые ничего не принимает и ничего не возвращает.
На 105 строке мы производим поиск контроллера “ProductViewController” в модуле “Main.storyboard”.
На 106 строке мы на найденном экземпляре контроллера обращаемся к свойству “delegate” и инициализируем его ссылкой на самого себя.
На 107 строке мы на найденном экземпляре контроллера обращаемся к свойству “id” и говорим, устанавливая значение “-1”, что мы создаем новый продукт.
Теперь откройте модуль “Main.storyboard”, в нем на контроллере “ProductViewController” выделите элемент управления с идентификатором “idTxt”.
В панели свойств на вкладке “Show the Attributes inspector” найдите свойство “User Interaction Enabled” и снимите галочку. Тем мы самым мы запретили редактирование значения этого поля.
Теперь перейдите в модуль “ProductViewController.swift”. Обновите в нем код, как это показано ниже:
Разберем добавленный код построчно.
На 19 строке мы добавили метод с именем “initData”, который ничего не возвращает и принимает аргумент с именем “id” типа “Int”.
На 20 строке мы проверяем идентификатор продукта, если он больше значения “-1”, значит продукт уже существует и содержит внутри себя данные, которые можно отобразить на текущей View.
На 22 строке мы ищем продукт по идентификатору продукта, переданному аргументом в методе в коллекции продуктов “supplies”.
С 23 по 25 строку мы заполняем элементы управления на пользовательском интерфейсе данными найденного продукта.
На 33 строке в методе “viewDidLoad” мы добавили вызов метода “initData” и передали ему аргумент поле “id”, которое мы задаем в контроллере “SuppliesViewController”.
Запустите приложение.
На анимации выше видно, что мы вошли в систему, открыли товар на редактирование, откредактировали товар и изменения сохранились. Затем удалили товар.
Теперь давайте проверим добавление нового товара:
Проверили, добавление нового товара работает хорошо. Но все равно есть один нюанс, наше приложение на русском языке. А когда выполняли на ячейки жест справа на- лево, то показывалась кнопка удалить “Delete” на английском, давайте исправим это, чтобы показывалось русское название “Удалить”.
Откройте модуль “SuppliesViewController.swift”, нам необходимо переопределить метод, объявленный в делегате “UITableViewDelegate” c именем “titleForDeleteConfirmationButtonForRowAt” и реализованный в классе “UITableViewController”. Добавьте следующий код, после метода “backToParent”:
Я думаю, этот метод не стоит разбирать построчно, тут и так все понятно.
Запустите приложение.
На этом завершим наш урок.
В следующем уроке мы разберем технологию 3D Touch и применим ее к нашему приложению для предварительного просмотра товаров. Добавим поле поиска в заголовок контроллера со списком товаров, реализуем поиск товаров. Реализуем протокол “UITextFieldDelegate”, чтобы скрывать клавиатуру при нажати кнопки “Ввод” на клавиатуре мобильного устройства. Разберем, как добавить собственную панель интрументов с кнопками.
Тайм-менеджмент для програміста, як встигнути всі?
Автор: Редакція ITVDN
Опять не выспались, потому что всю ночь кодили, учились, искали баги? Не сходили на обед, потому что дел выши крыши, плюс еще и весь небесный свод усыпан задачами? Забываете сделать обещанное, вспоминаете в последнюю секунду о встрече или запланированном мероприятии? Вы круглые сутки пашете, аки конь, но без результатов? Остановитесь, выдохните, вам надо заняться планированием и освоить азы тайм-менеджмента.
Не секрет, что в IT почва для планирования не шибко благодатная. Сколько бы ни придумали систем, все равно всегда есть место форс-мажору, заваленным проектам, испорченным нервам. В большинстве случаев так происходит не потому, что системы плохие, а потому что каждый конкретный человек, принимающий участие, например, в разработке, должен организовываться самостоятельно и уметь распределять дела, а у него не очень то и получается.
В этой статье мы поговорим с вами, как начать с себя, чтобы и у вас все успевалось, и у тех, с кем вы взаимодействуете, тоже. Как говорил кто-то очень умный и просвещенный, работать нужно не 12 часов в сутки, а головой. Ладно, мы знаем, это фраза Стива Джобса. =)
Итак, как же успевать все?
Планирование
Да, это банально, скучно и уныло. Но это работает. Более того, планирование смело можно считать одним из основных столпов грамотного распределения времени. Чтобы планирование было успешным, вам нужно любое место, куда писать:
- милый блокнот и мягкий карандаш,
- любимый девайс с приложением,
- "вездедоступный" онлайн календарь,
- почтовый клиент…
В общем, подойдет абсолютно любое человеческое изобретение, где есть даты и куда можно записать что-то. Выберите время суток, когда вы можете выделить 10 минут и спокойно поразмыслить о делах. Кому-то удобнее этим заниматься утром, перед работой, занятиями и т. д. Кто-то спокойнее отдается планированию вечером перед сном. Поставьте себе на это время напоминание. Сначала вам будет тяжело, обременительно, но с каждый днем привычка планировать будет укореняться, а разгребать завалы дел вы будете быстрее и успешнее.
Сложное и простое
Часто бывает, что одна задача очень большая и необъятная. И она такая страшная, что порой даже не знаешь, как к ней подойти. В таком случае выручает разбивание огромного на что-то маленькое. То есть, одной большой задачи - на подзадачи. Согласитесь, перенести огромный стеллаж с книгами за один раз — это непосильно и нереально. Но если вытащить книги, разложить их на стопки, а шкаф разобрать, то справиться с этим будет намного проще и быстрее.
Составляйте ваш список дел на день так, чтобы в нем было несколько простых, но нужных дел, например, разобрать почту, позвонить маме, заказать книгу и - более сложных. Выполнение простых задач обычно стимулирует и вы видите, как список сокращается, входите во вкус и завершаете все!
Мультизадачность
Она не работает и ее не существует. Это миф. И Юлий Цезарь со своими умениями не показатель. Наш мозг не умеет делать 2 дела одновременно и параллельно, ну не поддерживает он многопоточность. Даже когда вам кажется, что вы успешно сели на два стула - вы себя обманываете. Вы просто переключаетесь с одной задачи на другую. При этом с каждым переключением вспомнить о том, что вы делали по этой задаче раньше, становится все труднее и труднее и, как следствие, вы быстрее устаете. У вас болит голова, вы больше тратите времени, чтобы завершить начатое. И, в общем-то, либо ничего не успеваете, либо посредственно справляетесь с заданиями. Всегда делайте задачи одна за другой. Поверьте, последовательность — это идеальный помощник в решении сложных дел. Одно дело сделали — вычеркнули, наградили себя вкусным кофе, побежали делать следующее.
Приоритеты
Итак, мы договорились с вами, что будем планировать. Записали, все что нужно сделать, разбили сложные задачи на подзадачи, а как же сделать так, чтобы не перескакивать с одного дела на другое? В этом нам помогут приоритеты.
Как бы это странно ни звучало, но не бывает задач одинаково важных в один и тот же момент времени. Давайте рассмотрим на жизненном примере. Каждый водитель, подъезжающий к перекрестку, где есть светофор, помимо него также видит перед собой знак, который сообщает ему о том, что он на главной дороге, например, и может свободно ехать. Но вот вам ситуация: светофор горит красным, а знак говорит — поезжай. Стоять и ехать одновременно невозможно. Как решается эта ситуация? Правильно, приоритетами. У светофора он выше, значит, когда горит красный, какой бы знак ни висел, машина — стоит. Если же не работает светофор, то в силу вступает знак. И все.
Так же вы можете поступать со своими делами, решая, какая задача из списка более важная и нужная, а какая может подождать и подняться вверх только после выполнения более важной, а какую можно и вовсе не делать — никто не умрет, заводы и пароходы не остановятся.
Если же вам трудно сразу научиться расставлять приоритеты, почитайте о матрице Эйзенхауэра. Все ваши задачи можно разделить на 4 категории:
Срочные и важные,
Важные, но не срочные,
Срочные, но неважные,
Несрочные и неважные.
С таким подходом выполнять намеченное станет еще проще.
Время
У каждого из нас есть рутинные дела. И обычно мы приблизительно знаем, сколько тратим на них времени. Значит, мы можем четко прописывать, что на чтение почты и ответы на письма мы отводим 30 минут, например. Более того, ограничение по времени можно превратить в игру. Каждый раз пытаться побить рекорд предыдущего дня. Например, сегодня вы работали с почтой 30 минут, а завтра попробуйте справиться за 28. Так становится немного веселее.
Существуют дела без времени, например чтение профессиональной литературы. Тут проще, отведите себе какое-то время, например, час, поставьте таймер и садитесь читать. Время вышло — задача выполнена.
Следите за тем, как вы проводите свое время. Бывает, нам кажется, что мы так много работали, но ничего не сделали, хотя на самом деле, львиную долю рабочего дня мы просидели в социальных сетях. В общем-то, четкое планирование должно вас оградить от зыбучих песков интернета, но если вы уже и решили себя порадовать очередным спором в интернете, то также ограничьте это по времени.
Не делайте этого
Если вы понимаете, что то, что вам предстоит сделать, будет приносить вам страдания — не делайте это. Просто остановитесь и откажитесь, передайте эту задачу другому, забудьте о ней. Поверьте, всегда есть люди, которые сделают это за вас и никто не пострадает. Зато вы спокойно будете заниматься другими важными делами, а не страдать над подходами к выполнению намеченного. Если вся ваша работа причиняет вам боль, значит пора менять либо работу, либо профессию. Серьезно. В общем, старайтесь делать меньше того, что вам не нравится.
Отдыхайте
От работы дохнут кони, ну а я - бессмертный пони? Это про вас? Значит, выдохните. Знаете, если вы сегодня уйдете вовремя, небесная твердь не свалится, планеты не сойдут с орбит. А если вы завтра и вовсе возьмете отгул, то тоже ничего страшного не произойдет. Найдите для себя занятие, которое поможет вам расслабиться. И, да, запланируйте на него время. Это может быть все, что угодно: спорт, чтение, вышивание, прогулки, медитации и прочее, прочее. И спите, обязательно давайте мозгу время перезагрузиться и восстановиться, тогда вам все будет по плечу!
А теперь небольшой свод коротких правил, которые дополнят и уточнят все вышесказанное:
1. Планируйте и проставляйте приоритеты.
2. Ставьте на все задачи временные рамки, желательно, чтобы эти рамки не выходили за пределы рабочего времени (если мы говорим о рабочих делах).
3. Перерывы и отдых тоже планируйте.
4. В списке дел должны быть разные задачи: легкие, средние и посложнее.
5. Фиксируйте намеченное и вычеркиваете сделанное.
6. Мотивируйте себя музыкой, перерывом или чем-то еще приятным.
7. Разбивайте большие задачи на маленькие.
8. Суперконцентрация. Если нужно, а никак не получается — отключите все раздражители, уйдите в другую комнату, в общем, сделайте все, чтобы вы остались со своим заданием наедине.
9. Не делайте то, что вам не нравится.
10. Любите себя и берегите!
Паралакс для 2D гри без нервів та милиць
Автор: Дар'я Коновалова
Недавно в моей жизни начинающего разработчика игр появилась задача - сделать фон в игре, но не просто уныленький статичный бэкграунд, а параллакс. Да, эта чудо-красота применима не только в разработке сайтов, но и при создании игр. Попытка вдохновиться в гугле практически ничем не закончилась. Пришлось справляться с задачей собственными силами.
У меня получилось. Хочу поделиться с вами опытом.
Справедливо замечу, что параллакс — это не достояние веба. Еще в дремучие времена существования 8-битных игр параллакс успешно применялся для создания иллюзии объема в двухмерной игре. Коротко говоря, параллакс — это наслоение изображений, каждый слой движется со своей скоростью. Ближайший к игроку имеет самую высокую скорость, соответственно дальний (последний) — самую низкую.
Ну что, вроде, минимально в теории разобрались и даже нашли, откуда ноги растут, значит, мы готовы перейти непосредственно к практике и сотворить это чудо своими руками.
Что вам потребуется:
базовые знания Unity3D 5 (на уровне создания скриптов, добавления компонентов);
понимание С#;
3 или больше картинок в формате .png;
внимательность и желание.
Ладно, последнее не очень обязательно =)
По ссылке вы можете скачать необходимые изображения, а также уже готовый проект.
Подготовка
Запускаем Unity3D, создаем новый проект, называем его, например, Parallax2D.
Закидываем в папку Assets наши бэкграунды. Рекомендую сложить их в отдельную папку. В моем случае они лежат в Assets – StarSky.
Каждое изображение называем удобно и понятно. Я назвала их по порядку размещения (Background – задний фон, MiddleBackground – средний, TopBackground – верхний слой).
Для того, чтобы картинка перемещалась гладко, нам необходимо настроить ее в Inspector. Обратите внимание, этот этап очень важен, иначе все размажет, как звезды за иллюминатором Энтерпрайза на 3-й космической скорости.
В поле Texture Type выбираем тип Texture, во Wrap Mode отмечаем Repeat. И радостно тыкаем Apply. Без этого действия изменения не сохранятся, а потом можно долго недоумевать, почему же оно не работает. Совершаем эти телодвижения и для 2-х остальных текстур.
Подготовив картинки, переходим к этапу размещения их на сцене. Часто в этих ваших интерентах можно встретить совет - размещать их с помощью GameObject – Plane. Вы, конечно, можете потрудиться и заставить 3D объект нормально функционировать в 2D игре. Но, на самом деле, это все будет уныло, как последний эпизод «Звездных войн», а работать это чудовище будет чуть более быстро, чем аж никак. Поэтому я рекомендую долго не мучиться и использовать элемент UI – Canvas.
Canvas меняет размер фона автоматически, подстраивает его под размеры экрана гаджета, на котором запускают игру. Это избавляет нас от потребности писать скрипт, который будет отвечать за отслеживание размеров экрана и изменения размера фона.
В Hierarchy выбираем UI – Canvas. Собственно, если работать с Юнькой для вас не впервой, то вы явно знаете еще много других способов, как добавить в Hierarchy объект. Так что делайте это любым удобным способом.
Создав Canvas, добавляем чайлдами («внутрь» канвы) три Panel для 3-х наших фонов.
После добавления наша Hierarchy выглядит так:
Переименовываем Canvas и Panel, чтобы у всех были свои пароли и явки.
А теперь засучиваем рукава и беремся препарировать — подготавливать каждый компонент в Inspector.
Начнем с ParallaxBackground.
Изменяем Layer на Ignore Raycast, чтобы наш фон не реагировал на касания пальцами. Unity обязательно спросит, применить ли эти изменения ко всем «детям» — соглашаемся.
Далее переходим к компоненту Canvas.
Находим Render Mode, выбираем Screen Space – Camera. В Render Camera добавляем нашу текущую дефолтную камеру (Main Camera(Camera)).
Plane Distance ставим пока 110, но этот показатель проще отстроить во время теста. По факту — это расстояние от камеры до нашей канвы. То есть, изменяя его, мы будем получать разную глубину изображения.
Остальное не трогаем и переходим к Back.
В Rect Transform привязываем позицию к левому краю. Теперь наш фон будет всегда отстраиваться по одному краю, и мы избежим проблем с правильной позицией на разных устройствах.
Удаляем компонент Image (Script), вместо него добавляем Raw Image (Script) (напомню, Add Component – UI – Raw Image (Scriot)). В Texture добавляем картинку нашего самого последнего слоя.
Те же операции проделываем и для остальных слоев. Можно сделать немного проще, наколдовать изменения в первой Panel, дублировать (Ctrl + D), поставить в каждую свою текстуру, переименовать. Тут уже зависит от того, как вам будет удобнее.
Запускаем сцену — любуемся. Три картинки прекрасно легли друг на друга.
Немного черной магии
Теперь весь смак. Мы с вами напишем скрипт, который заставит наши картинки двигаться. Создаем новый скрипт, напомню, пишем на C#, назовем его BackgroundHelper. Он у нас будет один, поэтому нет смысла делать отдельную папку, кидаем его прямо в основную Assets. Открываем созданный скрипт и понеслась тяжкая работа на 5 строчек:
using UnityEngine;
using UnityEngine.UI; // обязательно добавляем библиотеку пользовательского интерфейса, без нее кино не будет
using System.Collections;
public class BackgroundHelper : MonoBehaviour {
public float speed = 0; //эта публичная переменная отобразится в инспекторе, там же мы ее можем и менять. Это очень удобно, чтобы настроить скорость разным слоям картинки
float pos = 0; //переменная для позиции картинки
private RawImage image; //создаем объект нашей картинки
void Start () {
image = GetComponent<RawImage>();//в старте получаем ссылку на картинку
}
void Update () {
//в апдейте прописываем как, с какой скоростью и куда мы будем двигать нашу картинку
pos += speed;
if (pos > 1.0F)
pos -= 1.0F;
image.uvRect = new Rect(pos, 0, 1, 1);
}
}
Сохраняем скрипт и добавляем его, как компонент к каждому слою.
Скорости у меня такие:
Back 0.0001
Middle 0.00015
Top 0.00024
Наслаждаемся успешной работой
Если все было сделано правильно, мы получим умопомрачительный эффект, от которого просто невозможно оторвать глаз.
У вас остались вопросы или возникли трудности? Пишите в комментариях.
Валідація AngularJS
Автор: Редакція ITVDN
Введение
Валидация достаточно часто вызывает затруднения с работой в веб-приложениях. Во многих случаях фреймворки должны быть использованы для валидации значений формы. Кроме того, эти фреймворки часто не работают во всех браузерах. AngularJS приходит с проверкой, построенной так, что теперь гораздо легче создать валидацию, которая работает во всех браузерах.
На практике
Использование angular-message
Чтобы использовать angular-message, нужно внести в ваш проект модуль:
angular.module("realestateApp", ["ngMessages"]);
Теперь ng-message будет доступен для использования.
Формы
Для инициализации процесса валидации, Вы должны начать с формы контейнера:
<form name="tenantForm">
Теперь внутри тега
можно добавить управление и логику на их проверку.
В сценариях валидации, как правило, есть несколько основных атрибутов. Те, которые стоит использовать: Required, Minimum, Maximum, Pattern, Email, Number, и URL.
Required
Этот атрибут заставляет form быть недействительным, если обязательное поле не вводится.
<input type="text" required />
Minimum Length
Этот атрибут указывает минимум символов для ввода до того, как значение будет принято.
<input type="text" ng-minlength=5 />
Maximum Length
Этот атрибут указывает максимальную длину или проверка будет неверна.
<input type="text" ng-maxlength=20 />
Pattern Matching
Эта функция позволяет согласовать совпадения при использовании Regex.
<input type="text" ng-pattern="[a-zA-Z]" />
Email Matching
Angular обеспечивает пользовательские функции по электронной почте.
<input type="email" name="email" ng-model="user.email" />
Number
Этот требует ввода в цифровом формате перед проверкой.
<input type="number" name="age" ng-model="user.age" />
URL
Этот требует ввода в ссылочном формате перед проверкой.
<input type="url" name="homepage" ng-model="user.url" />
Сообщение об ошибке
Ранее использовался error-container, но теперь можно использовать директиву ng-message:
<div ng-messages="tenantForm.Email.$error" ng-messages-include="messages.html" class="errors"></div>
Также нужен файл messages.html для хранения сообщений об ошибках:
<div class="messages">
<div ng-message="required">Required</div>
<div ng-message="minlength">Too short</div>
<div ng-message="maxlength">Too long</div>
<div ng-message="email">Invalid email address</div>
<div ng-message="compareTo">Must match the previous entry</div>
<div ng-message="number">Must be a number</div>
<div ng-message="url">Must be in URL format</div>
</div>
Сообщение об отмене ошибки
Иногда Вы должны иметь пользовательские сообщения об ошибках, не охватываемых messages.html. Вы можете сделать это, добавив тег span в диапазон для любого сообщения об ошибке. То, что должно отобразиться:
<div ng-messages="tenantForm.FirstName.$error" ng-messages-include="messages.html" class="errors">
<span class="messages" ng-message="minlength">Must be more than 3 characters</span>
<span class="messages" ng-message="maxlength">Must be more than 20 characters</span>
</div>
Собираем всё вместе
Теперь, объединив messages.html с index.html.
<form name="tenantForm" novalidate style="width: 500px">
<div class="row">
<div ng-repeat="tenant in tenant">
<div class="form-group">
<label>First Name: label>
<input type="text"
placeholder="First Name"
name="FirstName"
ng-model="tenant.FirstName"
ng-minlength=3
ng-maxlength=20 required />
<div ng-messages="tenantForm.FirstName.$error"
ng-messages-include="messages.html" class="errors">
<span class="messages"
ng-message="minlength">Must be more than 3 charactersspan>
<span class="messages"
ng-message="maxlength">Must be more than 20 charactersspan>
div>
div>
<div class="form-group">
<label>Home Phone: label>
<input type="number"
placeholder="Phone Number"
name="HomePhone"
ng-model="tenant.HomePhone"
ng-minlength=7
ng-maxlength=10 required />
<div ng-messages="tenantForm.HomePhone.$error"
ng-messages-include="messages.html" class="errors">
<span class="messages"
ng-message="minlength">Must be more than 7 digitsspan>
<span class="messages"
ng-message="maxlength">Must be less than 11 digitsspan>
div>
div>
<div class="form-group">
<label>Email: label>
<input type="email"
placeholder="Email"
name="Email"
ng-model="tenant.Email"
required />
<div ng-messages="tenantForm.Email.$error"
ng-messages-include="messages.html" class="errors">div>
div>
<div class="form-group">
<label>Webpage: label>
<input type="url"
placeholder="Webpage"
name="Webpage"
ng-model="tenant.Webpage"
required />
<div ng-messages="tenantForm.Webpage.$error"
ng-messages-include="messages.html" class="errors">
div>
div>
div>
div>
form>
Этот подход кажется чище, чем использование директивы ng-show. Кроме того, это уменьшит дублирование кода путем центрального места хранения ваших сообщений об ошибках, но в то же время используется для пользовательских сообщений.
Источник: http://www.codeproject.com/Articles/992545/AngularJS-Validation
Як створити веб-сайт за допомогою AJAX
Автор: Редакція ITVDN
Создания простого чата
Для начала создадим простой чат с помощью HTML&CSS и PHP&MySQL
Проектирование Базы Данных
Переходим к phpMyAdmin.
Создаем новую базу данных, называем “chatdb”.
Создаем новую таблицу, называя “Posts”, включая в себя 4 столбца:
“id” тип колонки INT, автоматическое увеличение на 1 (задаем A_I в ячейку с флажком) основной ключ (index);
“nick” тип колонки VARCHAR и длина 100;
“post_text” тип колонки TEXT;
“post_dt” тип колонки DATETIME по умолчанию является CURRENT_TIMESTAMP.
Создание веб-сайта.
Во-первых, создадим главную страницу. Список комментариев и отправка формы, используя кнопку. Всё будет находится в файле “index.php” . Давайте создадим этот файл и напишем что-то вроде этого:
<html>
<head>
head>
<body>
php
$mysqli = new mysqli("127.0.0.1", "root", "", "chatdb");
/* "127.0.0.1" is MySQL host name. In local servers (XAMPP, Ampps, etc.) it is 127.0.0.1. If you using a dedicated hosting, see it in admin panel. */
/* "root" and "" is login and password for DB's user. In local servers usually default DB user is "root" with empty password. */
/* "chatdb" is DB's name. */
/* Warning: in XAMPP you should manually run MySQL server (from xampp-control.exe) to get it work. */
$result = $mysqli->query("SELECT * FROM posts;");
?>
<style>
/* All CSS is very simplified. I provide it for example and no more.
* In Chrome it works tolerably, but in IE and Firefox it works very poorly.
*/
.content {
display: table;
width: 50%;
min-width: 400px;
height: 80%;
/* Center horizontally and vertically */
position: absolute;
left: 0; right: 0;
top: 0; bottom: 0;
margin: auto;
/* Design */
border: 1px solid;
background-color: silver;
padding: 5px;
}
/* For mobile devices */
@media (max-width: 400px) {
.content {
width: 100%;
min-width: 0;
padding: 0px;
}
}
style>
<div class="content">
<div id="comments" style="overflow-y: scroll; height: 100%;">
php
if ($result) {
while ($post = $result->fetch_object()){
$nick = $post->nick;
$post_dt = $post->post_dt;
$post_text = $post->post_text;
echo "<b>$nickb> ($post_dt):<br>";
echo "$post_text<br>";
echo "<br>";
}
$result->close();
}
?>
div>
php
$mysqli->close();
?>
<form action="post.php" method="post" style="height: 0; display: table-row;">
Nick:<br>
<input type="text" name="nick" style="width: 100%;">input><br>
<br>
Text:<br>
<textarea name="text" style="width: 100%;">textarea><br>
<br>
<input type="submit">input>
form>
div>
<script type="text/javascript">
var divComments = document.getElementById('comments');
divComments.scrollTop = divComments.scrollHeight;
script>
body>
html>
2. Дальше давайте создадим “post.php” файл и напишем такое:
php
$nick = $_POST['nick'];
$post_text = $_POST['text'];
$mysqli = new mysqli("127.0.0.1", "root", "", "chatdb");
$nick = $mysqli->real_escape_string($nick);
$nick = htmlspecialchars($nick);
$post_text = $mysqli->real_escape_string($post_text);
$post_text = htmlspecialchars($post_text);
$mysqli->query("INSERT INTO posts (nick, post_text) VALUES ('$nick', '$post_text');");
$mysqli->close();
/* Redirect To Main Page */
header('Location: ' . $_SERVER['HTTP_REFERER']);
?>
3. Откроем ваш чат в любом продвинутом браузере. Вся работа:
4. Напишите ник и текст, а потом нажмите на кнопку отправки.
Когда отправили форму, сразу же происходит перенаправление формы главной страницы в “post.php”. “Post.php” моментально наполняет данными таблицу и перенаправляет на главную страницу. Также “post.php” содержит начальный XSS и SQL защищенный вход. Для упрощения не отправляем клиентам время с JS на “post_dt” на внесения данных, а указываем значение по умолчанию – CURRENT_TIMESTAMP, который предоставляет нынешнюю дату и время на сервер.
Когда количество комментариев больше, чем экран может вместить, то комментарии переполнены в div, используется вертикальный скроллбар. Во время загрузки страницы JS автоматически опускает скроллбар вниз к недавнему комментарию.
Что тут не так?
Проблема номер 1. Новый непрочитанный комментарий от пользователя не загружается в базу данных автоматически, без ручной перезагрузки страницы.
Это очень, очень серьезная проблема для любого чата.
Как это исправить?
Очевидно, один из путей сделать это - использовать HTTP-запрос к фоновой работе автоматически (с помощью JS) и асинхронно, то есть это один из способов обеспечить выполнение AJAX в любой form.
Проблема номер 2. Также неправильно то, что кнопка отправки перезагружает страницу (перенаправляет на вторую страницу и следующим шагом возвращает назад).
При перезагрузке страницы сбрасывает “Nick” в поле (также, как любые другие изменения, те, что сделали с пользователем)
Как это исправить?
Конечно, можно обеспечить выполнение обхода для сохранения изменений и перезагрузки на перезагружаемой странице. Но не эффективнее ли устранить причину, чем последствие? Можно только убрать перезагружаемую страницу и эта проблема будет решена автоматически.
Проблема номер 2.1. Видите ли что-то необычное тут?
Это слишком быстро?
Дело с чатом очень простое – загружаем только два маленьких текстовых параметра. Что делать, если нужно улучшить чат с помощью добавления присоединения, а конкретно - изображений и видео? Видео может иметь объем в размере нескольких мегабайт, что тогда в этом случае?
Давайте попробуем. Давайте немного изменим “post.php”, после $mysql->query() добавив это:
for ($i = 0; $i < 1000000000; $i++) { }
Старайтесь размещать какие-либо комментарии. Что мы видим? Нет, UI не остановилось (заморозилось), но браузер ждет до того, как закончится подключение:
Да, если “post.php” вызывает какую-то необработанную ошибку, то чат исчезает, и пользователь видит пустое окно с сообщением о непонятной ошибке. Чтобы вернуться к чату, пользователю стоит нажать кнопку “Back” в браузере. И это проблема номер 2.2.
Давайте уберем петлю с “post.php” и исправим эти проблемы.
Реализация легкого AJAX в простой чат
Автоматическое обновление комментариев
Эта веб-страница без AJAX? Это веб-страница, которая полностью перезагружается.
Эта веб-страница c AJAX? Это веб-страница, что перезагружается частично.
Где же взять части этих страниц? Стоит разделить нашу страницу на части, в этом случае сервер генерирует страницу частично.
Страница будет состоять из двух частей – блок комментариев и другой контент страницы. Комментарии будут загружаться и перезагружаться отдельно от другой страницы.
Давайте сделаем первую часть, то есть блок комментариев.
Для начала создадим пустой файл, назовем “getcomment.php”.
Дальше переходим в “index.php” для того, чтобы вырезать комментарии и далее отделить его.
1. Вырезаем инициализированный блок MySQL с “index.php"
php
$mysqli = new mysqli("127.0.0.1", "root", "", "chatdb");
/* "127.0.0.1" is MySQL host name. In local servers (XAMPP, Ampps, etc.) it is 127.0.0.1. If you using a dedicated hosting, see it in admin panel. */
/* "root" and "" is login and password for DB's user. In local servers usually default DB user is "root" with empty password. */
/* "chatdb" is DB's name. */
/* Warning: in XAMPP you should manually run MySQL server (from xampp-control.exe) to get it work. */
$result = $mysqli->query("SELECT * FROM posts;");
?>
вставляем в “getcomments.php”.
2. Следующее, вырежем контент div c комментариями с “index.php”:
php
if ($result) {
while ($post = $result->fetch_object()){
$nick = $post->nick;
$post_dt = $post->post_dt;
$post_text = $post->post_text;
echo "<b>$nickb> ($post_dt):<br>";
echo "$post_text<br>";
echo "<br>";
}
$result->close();
}
?>
И вставим (добавим) в “getcomments.php” после инициализации MySQL.
3. Дальше, вырежем недалекий блок MySQL с “index.php”:
php
$mysqli->close();
?>
Вставим его в конец файла “getcomments.php”.
4. Наконец-то, убираем JS-скрипт, который опускает скролл в самый низ.
<script type="text/javascript">
var comments = document.getElementById('comments');
comments.scrollTop = comments.scrollHeight;
script>
Не переживай, это только временно.
5. Сделано. Теперь имеем что-то наподобие этого:
<html>
<head>
head>
<body>
<style>
/* All CSS is very simplified. I provide it for example and no more.
* In Chrome it works tolerably, but in IE and Firefox it works very poorly.
*/
.content {
display: table;
width: 50%;
min-width: 400px;
height: 80%;
/* Center horizontally and vertically */
position: absolute;
left: 0; right: 0;
top: 0; bottom: 0;
margin: auto;
/* Design */
border: 1px solid;
background-color: silver;
padding: 5px;
}
/* For mobile devices */
@media (max-width: 400px) {
.content {
width: 100%;
min-width: 0;
padding: 0px;
}
}
style>
<div class="content">
<div id="comments" style="overflow-y: scroll; height: 100%;">
div>
<form action="post.php" method="post" style="height: 0; display: table-row;">
Nick:<br>
<input type="text" name="nick" style="width: 100%;">input><br>
<br>
Text:<br>
<textarea name="text" style="width: 100%;">textarea><br>
<br>
<input value="Submit" type="submit">input>
form>
div>
body>
html>
getcomments.php:
php
$mysqli = new mysqli("127.0.0.1", "root", "", "chatdb");
/* "127.0.0.1" is MySQL host name. In local servers (XAMPP, Ampps, etc.) it is 127.0.0.1. If you using a dedicated hosting, see it in admin panel. */
/* "root" and "" is login and password for DB's user. In local servers usually default DB user is "root" with empty password. */
/* "chatdb" is DB's name. */
/* Warning: in XAMPP you should manually run MySQL server (from xampp-control.exe) to get it work. */
$result = $mysqli->query("SELECT * FROM posts;");
?>
Вероятно, теперь наша страница разделена.
Давайте проверим её. Переходим http (точнее ССЫЛКА для локального сервера).
Дальше, переходим на главную страницу, http:///index.php
Это удивительно! Всё удачно получилось, разделив страницу на части за пару минут!
Не останавливаемся на этом. Теперь “glue” эти части с помощью AJAX. Как это сделать?
1. Для начала стоит создать пустой JS-скрипт на главной странице:
<script type="text/javascript">
alert('Test');
script>
Добавив это после тега div, перед закрывающимся тегом .
2. Сделаем HTTP GET запрос от JS к “getcommet.php”
Для этого используем XMLHtttpRequest (XHR) класс:
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open('GET', '/getcomments.php', false);
xhr.send(null);
if (xhr.status == 200) {
alert(xhr.responseText);
}
script>
Это работает, но не читает старые версии IE те, что не поддерживают такую инициализацию.
Для получения более кросс-браузерного пути переходим в
и добавляем это:
<script type="text/javascript">
function getXmlHttp(){
var xmlhttp;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
};
script>
И главный скрипт (в конце)
<script type="text/javascript">
var xhr = getXmlHttp();
xhr.open('GET', '/getcomments.php', false);
xhr.send(null);
if (xhr.status == 200) {
alert(xhr.responseText);
}
script>
Как видно, теперь JS получает контент со страницы “getcomments.php” и показывает это в предупреждении.
2.1. Вопрос: «Это на самом деле AJAX (Asynchronous Javascript And Xml)?»
Это AJAX, потому что запрос сервера отформатирован в HTML (который основан на XML).
Но действительно ли это AJAX, это асинхронно?
Проверим. Добавим эти уже знакомые строки в любое место между в “getcomments.php”:
for ($i = 0; $i < 1000000000; $i++) { }
Что теперь видно на загружаемой странице?
Вначале страница зависает, его замораживает UI (становится не реагирующим на нажатие левой/правой кнопки мыши):
Дальше Chrome показывает навязчивое всплывающие окно, сообщающее об удалении страницы:
Это не AJAX! Это JAX! Как его сделать асинхронным?
К счастью, ХHR также поддерживает асинхронный режим:
<script type="text/javascript">
var xhr = getXmlHttp();
xhr.open('GET', '/getcomments.php', true); /* true for asynchronous */
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if(xhr.status == 200) {
alert(xhr.responseText);
}
}
};
xhr.send(null);
script>
В этом случае браузер не ждет ответа с главного потока пользовательского интерфейса, он запускает в другом (асинхронно) и вызываемым событием “onreadystatechange” в главном контексте UI.
Теперь всё в порядке, страница полностью доступна, пока запрос запущен, и после ответа получит предупреждение.
for ($i = 0; $i < 1000000000; $i++) { }
И продолжаем работу.
3. Добавляем этот контент в div вместо предупреждения. Заменить это:
alert(xhr.responseText);
C этим:
var divComments = document.getElementById('comments');
divComments.innerHTML = xhr.responseText;
Возвращаем назад, клиент видит “glued” страницу с блоком комментариев.
4. Дальше следует установить интервал для автоматической проверки новых комментариев время от времени… и также восстановить удаленный сценарий автоматической прокрутки.
<html>
<head>
<script type="text/javascript">
function getXmlHttp() {
var xmlhttp;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
};
script>
head>
<body>
<style>
/* All CSS is very simplified. I provide it for example and no more.
* In Chrome it works tolerably, but in IE and Firefox it works very poorly.
*/
.content {
display: table;
width: 50%;
min-width: 400px;
height: 80%;
/* Center horizontally and vertically */
position: absolute;
left: 0; right: 0;
top: 0; bottom: 0;
margin: auto;
/* Design */
border: 1px solid;
background-color: silver;
padding: 5px;
}
/* For mobile devices */
@media (max-width: 400px) {
.content {
width: 100%;
min-width: 0;
padding: 0px;
}
}
style>
<div class="content">
<div id="comments" style="overflow-y: scroll; height: 100%;">
div>
<form action="post.php" method="post" style="height: 0; display: table-row;">
Nick:<br>
<input type="text" name="nick" style="width: 100%;">input><br>
<br>
Text:<br>
<textarea name="text" style="width: 100%;">textarea><br>
<br>
<input value="Submit" type="submit">input>
form>
div>
<script type="text/javascript">
var divComments = document.getElementById('comments');
function loadComments() {
var xhr = getXmlHttp();
xhr.open('GET', '/getcomments.php', true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.responseText !== divComments.innerHTML) {
divComments.innerHTML = xhr.responseText;
divComments.scrollTop = divComments.scrollHeight;
}
}
}
};
xhr.send(null);
};
loadComments();
setInterval(loadComments, 1000)
script>
body>
html>
Теперь проблема исправить это. Новые комментарии с другого пользовательского чата (другие вкладки браузеров, окон и экземпляров) получают каждые 1000 миллисекунды (1 секунду) автоматически.
Но отправка комментариев уже вызывает перезагрузку нашей страницы.
Отправка комментариев без перезагрузки
Как написано выше, XHR помогает отправлять HTTP GET-запросы без перезагрузки страницы и GUI заморозки (асинхронно).
Теперь отправляем HTTP-запрос асинхронно, но на этот раз POST запрос, а не GET.
И, естественно, XHR позволяет это. Используем метод send(). Для GET указываем null. Для POST устанавливаем запрос “body”.Также нужно добавить “Content-Type:application/x-www-from-urlencoded” в header для того, чтобы разрешить серверу знать, какой формат использовать для отправки данных.
Заметка: если не знаете, что отправлять, то можно захватить регулярный запрос с помощью “Fiddler” или же любой другой HTTP-перехватчик и только просимулировать запрос. HTTP-перехватчик — это незаменимый инструмент для работы с HTTP/HTTPS. Это позволяет увидеть все headers и bodies по всем HTTP(S)-запросам, что отправляются в систему. Лучше использовать “Fiddler”, это бесплатное, современное и очень простое приложение, что может поддерживать HTTP/HTTPS и оба Win x86/x64.
1. Для начала создадим пустой JS скрипт в HTML. Разместить до самого
тега, потому что этот скрипт будет использован для отправки формы комментария (форма будет вызывать этот скрипт при отправке).2. В этом скрипте, реализуем функцию, что будет отправлять ник и комментарии в “post.php”
<script type="text/javascript">
function postComment(nick, text) {
var xhr = getXmlHttp();
xhr.open('POST', '/post.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
/* it isn't required to add comment to DOM manually, it will done automatically on next refresh via AJAX */
}
}
};
xhr.send('nick=' + nick + '&text=' + text); /* joining the data in format simulates form */
};
script>
3. Следующие, перезагружая страницу при отправке:
... onsubmit="return false;">
Также добавим вызов “postComment’s”:
action="post.php" method="post" style="height: 0; display: table-row;" onsubmit="postComment(this.nick.value, this.text.value); return false;">
4. Наконец, необязательно, но можно убрать “action” и “method” с формы:
<form style="height: 0; display: table-row;" onsubmit="postComment(this.nick.value, this.text.value); return false;">
5. Результат:
<html>
<head>
<script type="text/javascript">
function getXmlHttp() {
var xmlhttp;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
};
script>
head>
<body>
<style>
/* All CSS is very simplified. I provide it for example and no more.
* In Chrome it works tolerably, but in IE and Firefox it works very poorly.
*/
.content {
display: table;
width: 50%;
min-width: 400px;
height: 80%;
/* Center horizontally and vertically */
position: absolute;
left: 0; right: 0;
top: 0; bottom: 0;
margin: auto;
/* Design */
border: 1px solid;
background-color: silver;
padding: 5px;
}
/* For mobile devices */
@media (max-width: 400px) {
.content {
width: 100%;
min-width: 0;
padding: 0px;
}
}
style>
<div class="content">
<div id="comments" style="overflow-y: scroll; height: 100%;">
div>
<script type="text/javascript">
function postComment(nick, text) {
var xhr = getXmlHttp();
xhr.open('POST', '/post.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
/* it isn't required to add comment to DOM manually, it will done automatically on next refresh via AJAX */
}
}
};
xhr.send('nick=' + nick + '&text=' + text);
};
script>
<form style="height: 0; display: table-row;" onsubmit="postComment(this.nick.value, this.text.value); return false;">
Nick:<br>
<input type="text" name="nick" style="width: 100%;">input><br>
<br>
Text:<br>
<textarea name="text" style="width: 100%;">textarea><br>
<br>
<input value="Submit" type="submit">input>
form>
div>
<script type="text/javascript">
var divComments = document.getElementById('comments');
function loadComments() {
var xhr = getXmlHttp();
xhr.open('GET', '/getcomments.php', true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.responseText !== divComments.innerHTML) {
divComments.innerHTML = xhr.responseText;
divComments.scrollTop = divComments.scrollHeight;
}
}
}
};
xhr.send(null);
};
loadComments();
setInterval(loadComments, 1000)
script>
body>
html>
Теперь простая реализация AJAX подошла к концу. Это ещё не конец, это только начало. Есть ещё достаточно всего для изучения и улучшения.
Індексація стовпців у Redis
Автор: Редакція ITVDN
Введение
В отличие от memcache, Redis может быть использован в качестве постоянного хранилища, а не только как временный кэш. Так случилось, что Redis — это невероятно быстрая база данных, дающая поразительно лучшую производительность Вашему приложению в случае правильной настройки. В качестве предостережения хотелось бы добавить, что при работе с Redis в качестве основного хранилища таится много рисков, и эти риски значительно увеличиваются в случае некорректной настройки. Настоятельно рекомендуем предварительно провести тщательное исследование Redis перед тем, как заменять Вашу текущую базу данных в пользу Redis.
Несмотря на преимущество Redis в виде невероятной скорости работы, дающейся Вашему приложению, есть факт, который стоит обязательно учесть – Redis – это фундаментальное хранилище ключей/значений, и он не поддерживает индексы. И Вы можете столкнутся с непредвиденным «челленджем» при попытке проиндексировать Ваши значения. Однако, Вы можете обойти эти ограничения с помощью удивительно полезных предоставляемых Redis типов данных. В этой статье буду рассмотрены способы использования каждых наборов и отсортированных наборов для каждого индекса, и как сортировать записи по датам, а также извлекать строки в пределах диапазона дат.
О комплексных типах Redis
Redis поддерживает несколько комплексных типов — списки, «сеты», отсортированные «сеты» и хэши. В текущей статье не будут упомянуты типы, за исключением «сетов» и отсортированных «сетов». Собственно, «сеты» — это коллекция уникальных неупорядоченных значений. Между тем, отсортированные сеты немного отличаются от обычных сетов. Они позволяют хранить значения, к примеру, с баллами и, таким образом, все члены отсортированного набора можно будет запросить с помощью баллов или диапазона баллов. Это может быть очень удобным при хранении, например, даты. Происходит конвертация каждого одиночного элемента (даты) и хранится в его «тике». Таким образом получается упорядоченный набор значений. Затем можно использовать команду ZRANGEBYSCORE REDIS для получения значений, которые подходят под определённый диапазон.
Redis, как хранилище ключевых значений, может также хранить элемент с любым типом данных в базе данных до тех пор, пока этот элемент будет иметь уникальный ключ, определённый для него, вне зависимости от того, какого типа этот элемент – строковый или числовой.
Использование в коде
Код не так страшен, как может показаться с первого взгляда. Например, целый объект сохраняется с указателем ко всем индексам. Подход таков, что объект может быть сохранён, как скалярная строка с id в различных индексах.
В коде есть класс «Person», позволяющий сохранять объекты класса «Person» с параметрами, например, имя, пол, страна проживания и дата рождения. Класс написан достаточно просто, на мелкие детали не придётся обращать внимание. Также в коде есть статический класс «RedisAdaptor», который содержит в себе вспомогательные функции для сохранения класса «Person» и вызова его объектов.
Основная программа
В основной программе имеется цикл, который прогоняется ежедневно на протяжении указанного года – 1971 и создаёт новый объект класса «Person» с указанием даты рождения. В случае с полом мы оставляем один единственный объект с указанием женского или мужского пола. Для страны (учитывая, что определены только 3 страны) каждый объект класса «Person» проживает в Индии, США и Англии. В качестве имени используется строка, генерируемая случайным образом.
static void Main(string[] args)
{
const int YEAR = 1971;
// We create one Person object for every single day in the given year.
for (int month = 1; month <= 12; ++month)
{
for (int day = 1; day <= 31; ++day)
{
try
{
// Get any random name:
string name = Util.GetAnyName();
// And a DoB:
DateTime dob = new DateTime(YEAR, month, day);
// As for the gender, let's alternate:
Gender gender = Gender.FEMALE;
if (day % 2 == 0)
{
gender = Gender.MALE;
}
// And the country, let's round-robin between all three:
Country country = Country.INDIA;
if (day % 3 == 1)
{
country = Country.USA;
}
else if (day % 3 == 2)
{
country = Country.GB;
}
// Create a new Person object:
Person person = new Person(name, gender, country, dob);
//Console.WriteLine ("Created new Person object: {0}", person);
// We call the function that will store a new person in Redis:
RedisAdaptor.StorePersonObject(person);
}
catch (Exception)
{
// If the control reaches here, it means the date was illegal.
// So we just shrug your shoulders and move on to the next date.
continue;
}
}
}
// At this point, we have 365 Person objects as a sorted set in our Redis database.
// Next, let's take a date range and retrieve Person objects from within that range.
DateTime fromDate = DateTime.Parse("5-May-" + YEAR);
DateTime toDate = DateTime.Parse("7-May-" + YEAR);
List persons = RedisAdaptor.RetrievePersonObjects(fromDate, toDate);
Console.WriteLine("Retrieved values in specified date range:");
foreach (Person person in persons)
{
Console.WriteLine(person);
}
// Next, let's select some folks who are female AND from the USA.
// This calls for a set intersection operation.
List personsSelection = RedisAdaptor.RetrieveSelection(Gender.FEMALE, Country.USA);
Console.WriteLine("Retrieved values in selection:");
foreach (Person person in personsSelection)
{
Console.WriteLine(person);
}
}
В классе «Redis Adaptor» есть одиночная функция, используемая для хранения и индексации значений, прошедших через него, и функция для запрашивания значений по диапазонам дат, полу и стране. Также в программе имеется несколько статических полей и констант для данных.
Учтите, что индексация уже произошла в процессе сохранения. В этом участке мы проиндексировали пол, страну и дату рождения.
static class RedisAdaptor
{
const string REDIS_HOST = "127.0.0.1";
private static ConnectionMultiplexer _redis;
// Date of birth key:
const string REDIS_DOB_INDEX = "REDIS_DOB_INDEX";
// Gender keys:
const string REDIS_MALE_INDEX = "REDIS_MALE_INDEX";
const string REDIS_FEMALE_INDEX = "REDIS_FEMALE_INDEX";
// Country keys:
const string REDIS_C_IN_INDEX = "REDIS_C_IN_INDEX";
const string REDIS_C_USA_INDEX = "REDIS_C_USA_INDEX";
const string REDIS_C_GB_INDEX = "REDIS_C_GB_INDEX";
static RedisAdaptor()
{
// First, init the connection:
_redis = ConnectionMultiplexer.Connect(REDIS_HOST);
}
public static void StorePersonObject(Person person)
{
// We first JSONize the object so that it's easier to save:
string personJson = JsonConvert.SerializeObject(person);
//Console.WriteLine ("JSONized new Person object: {0}", personJson);
// And save it to Redis.
// First, get the database object:
IDatabase db = _redis.GetDatabase();
// Bear in mind that Redis is fundamentally a key-value store that does not provide
// indexes out of the box.
// We therefore work our way around this by creating and managing our own indexes.
// The first index that we have is for gender.
// We have two sets for this in Redis: one for males and the other for females.
if (person.Gender == Gender.MALE)
{
db.SetAdd(REDIS_MALE_INDEX, personJson);
}
else {
db.SetAdd(REDIS_FEMALE_INDEX, personJson);
}
// Next, we index by country.
if (person.Country == Country.INDIA)
{
db.SetAdd(REDIS_C_IN_INDEX, personJson);
}
else if (person.Country == Country.USA)
{
db.SetAdd(REDIS_C_USA_INDEX, personJson);
}
else if (person.Country == Country.GB)
{
db.SetAdd(REDIS_C_GB_INDEX, personJson);
}
// Next, we need to create an index to be able to retrieve values that are in a particular
// date range.
// Since we need to index by date, we use the sorted set structure in Redis. Sorted sets
// require a score (a real) to save a record. Therefore, in our case, we will use the
// DoB's `ticks' value as the score.
double dateTicks = (double)person.DoB.Ticks;
db.SortedSetAdd(REDIS_DOB_INDEX, personJson, dateTicks);
}
public static List RetrievePersonObjects(DateTime fromDate, DateTime toDate)
{
// First. let's convert the dates to tick values:
double fromTicks = fromDate.Ticks;
double toTicks = toDate.Ticks;
// And retrieve values from the sorted set.
// First, get the database object:
IDatabase db = _redis.GetDatabase();
RedisValue[] vals = db.SortedSetRangeByScore(REDIS_DOB_INDEX, fromTicks, toTicks);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
public static List RetrievePersonObjects(Gender gender)
{
// First, get the database object:
IDatabase db = _redis.GetDatabase();
string keyToUse = gender == Gender.MALE ? REDIS_MALE_INDEX : REDIS_FEMALE_INDEX;
RedisValue[] vals = db.SetMembers(keyToUse);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
public static List RetrievePersonObjects(Country country)
{
// First, get the database object:
IDatabase db = _redis.GetDatabase();
string keyToUse = REDIS_C_IN_INDEX;
if (country == Country.USA)
{
keyToUse = REDIS_C_USA_INDEX;
}
else if (country == Country.GB)
{
keyToUse = REDIS_C_GB_INDEX;
}
RedisValue[] vals = db.SetMembers(keyToUse);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
public static List RetrieveSelection(Gender gender, Country country)
{
// First, get the database object:
IDatabase db = _redis.GetDatabase();
string keyToUseGender = gender == Gender.MALE ? REDIS_MALE_INDEX : REDIS_FEMALE_INDEX;
string keyToUseCountry = REDIS_C_IN_INDEX;
if (country == Country.USA)
{
keyToUseCountry = REDIS_C_USA_INDEX;
}
else if (country == Country.GB)
{
keyToUseCountry = REDIS_C_GB_INDEX;
}
RedisKey[] keys = new RedisKey[] { keyToUseGender, keyToUseCountry };
RedisValue[] vals = db.SetCombine(SetOperation.Intersect, keys);
List opList = new List();
foreach (RedisValue val in vals)
{
string personJson = val.ToString();
Person person = JsonConvert.DeserializeObject(personJson);
opList.Add(person);
}
return opList;
}
}
Каждый ключ константы определяется в статическом классе, как, например, REDIS_DOB_INDEX, REDIS_MALE_INDEX, REDIS_FEMALE_INDEX и остальные, всё это – ключи к индивидуальным сетам в хранилище Redis.
Однажды, когда мы сохранили значения и создали индексы для сетов в Redis, мы сможем запрашивать их, используя различные версии перегруженных функций RetrievePersonObjects с параметрами – диапазоном дат, полом и страной.
Запрос по полу достаточно прост: основываясь на определении пола, мы «погружаемся» в один из двух гендерных сетов и получаем запрошенное значение. Точно та же процедура используется в отношении индекса стран.
Чтобы извлечь значения диапазона дат, используется метод SortedSetRangeByScore в объекте базы данных. Он принимает три аргумента: первый – имя сортированного сета, минимальные и максимальные значения.
Ещё одна интересная фича в работе с сетами Redis – великолепные семантические сеты. С их помощью можно определять два и более сетов, в результате чего БД Redis делает объединение, пересечение или определяет разность сетов. В последней секции кода посмотрите на функцию снизу – «RetrieveSelection», которая декларирует два параметра – пол и страна. Эта функция соответственно возвращает два параметра – пол и страну.
Источник: http://www.codeproject.com/Articles/1072137/Indexing-Columns-in-Redis
Використання форм у HTML
Автор: Редакція ITVDN
Введение
Формы используются для сбора информации, внесенной пользователем. Введенные данные взаимодейстуют с веб-приложениями, например, или когда нужно отправлять информацию в Интернет.
Формы сами по себе не очень полезные. Вместе с языком программирования их используют для обработки информации, введенной пользователем. Эти разнообразные скрипты нуждаются в других языках, отличающихся от HTML и CSS.
Теги form, input, textarea, select и option – базовые теги для форм в HTML.
Form
Тег form формирует такой себе «бланк». Если используется пользовательская форма для отправки данных, то нужно описать атрибут action для указания, куда контент будет отправлен.
Атрибут method указывает форме, как данные будут отправляться на сервер, также имеет дефолтное значение get, а также post, что фактически незаметно передает информацию о форме.
Get применяется для более коротких участков неконфиденциальной информации с сайта. Например, поиск будет отображаться в адресе страницы результатов поиска. Значение post - для более продолжительных, более защищенных материалов, таких как контактные формы, например.
Вот элемент формы будет выглядеть примерно так:
<form action="processingscript.php" method="post">
form>
Input
Тег Input - чуть ли не важнейшее в формах. Он может принимать огромное число значений, самые распространенные:
<input type=”text”> или просто <input> - стандартное текстовое поле. Также может иметь атрибут value, что превращает исходный текст в textbox.
<input type=”password”> - похожий на textbox, однако символы скрыты от пользователя.
<input type=”checkbox”> - кнопка с флажком, пользователь может задать режим вкл/выкл. Также может иметь атрибут checked ( <input type=”checkbox” checked> ), делает флажок «включенным».
<input type=”radio”> - похожий на checkbox, пользователь может выбрать только одну радиокнопку из группы. Также может иметь атрибут checked.
<input type=”submit”> - кнопка, что отправляет форму. Пользователь может изменять исходный текст формы через атрибут value, например
<input type="submit" value="Ooo. Look. Text on a button. Wow">
Обратите внимание на то, что тег input как и img, и br не имеет закрывающегося тега.
Textarea
Textarea – по сути, большое многострочное текстовое поле. Через атрибуты rows и cols задается число строк и столбцов соответственно, хотя можно управлять размером поля через CSS.
<textarea rows="5" cols="20">A big load of texttextarea>
Select
Тег Select в паре с option создает выпадающий список.
<select>
<option>Option 1option>
<option>Option 2option>
<option value="third option">Option 3option>
select>
Выбранное значение отправляется при подтверждении формы. Этим значением будет текст, заключенный в тег option, но будет отослано значение атрибута value, если он явно задан. Так, из примера выше, если выбран первый пункт, «Option 1» будет отправлено, если же третий -
Тег option может иметь атрибут selected, аналогично как checked для checkbox и радиокнопок. Например, <option selected>Rodentoption> будет изначально выбран вариант “Rodent”.
Names
Все вышеописанные теги будут красиво размещаться на странице, но, если подключить скрипт для обработки формы – все они будут проигнорированы. Так случится потому, что поля формы должны иметь уникальные имена. Так что нужно добавить атрибут name во все поля:
<input type="text" name="talkingsponge">
Пример формы:
<form action="contactus.php" method="post">
<p>Name:p>
<p>
<input type="text" name="name" value="Your name">p>
<p>Comments: p>
<p>
<textarea name="comments" rows="5" cols="20">Your commentstextarea>p>
<p>Are you:p>
<p>
<input type="radio" name="areyou" value="male">
Malep>
<p>
<input type="radio" name="areyou" value="female">
Femalep>
<p>
<input type="radio" name="areyou" value="hermaphrodite">
An hermaphroditep>
<p>
<input type="radio" name="areyou" value="asexual">
Asexualp>
<p>
<input type="submit">p>
form>
Источник: http://www.htmldog.com/guides/html/beginner/forms/