iOS plist преобразование в словарь
Задача
Нужно загрузить данные из plist файла, перевести эти данные в словарь (Dictionary) и использовать этот словарь для вывода таблицы. В итоге, должно получится такое приложение:
Решение
Создание проекта
Надеюсь читататедь сможет собрать проект по моему описанию. Я не буду как в предыдущих статьях описывать каждый клик в среде разработки Xcode. Однако, если возникнут сложности, то можно прочитать предыдущие статьи на сайте в разделе «Уроки на реальных примерах».
Создайте проект в Xcode, используюя в качестве шаблона Single View Application. К имеющемуся ViewController добавьте из библиотеки объектов TableViewController. Main.storyboard должен выглядет как-то так:
Далее нужно добавить класс для работы с TableViewController, присвоить его контроллеру в Storyboard. Также нужно добавить две кнопки и один Label из библиотеки элементов на ViewController. Разместите эти элементы и задайте нужные констрейнты:
Добавьте outlet для Label и два action для кнопок.
Также выполните ctrl — перетягивание с кнопки «Показать таблицу» на DictTableViewController и укажите тип перехода Show.
Кликните на значке перехода и укажите имя для него, например ShowTableSegue.
И последнее в нашей подготовке — кликните на ячейке внутри Table View Controller и задайте Reuse Identifier — например DictCell. Можно еще для удобства увеличить высоту ячейки в контроллере.
iOS Plist
Как было написано ранее plist — это просто определение термина. Он означает AnyObject, который, является коллекцией объектов, которыми может быть ТОЛЬКО один из следующих типов: NSString, NSArray, NSDictionary, NSNumber, NSData, NSDate.
Другими словами, plist — это коллекция из определенных видов элементов, которые являются стандартными типами. Эта коллекция может содержать любое количество этих самых элементов.
В нашем примере мы будем использовать plist-файл, из которого мы загрузим данные и заснем их в таблицу. Опять же, для нашего примера мы создадим вручную нужный нам файл, но у вас файл скорее всего уже будет существовать, поэтому вы просто можете добавить его в проект и перейти к следующему пункту статьи.
Перейдите в меню File -> New -> File… Добавьте Property List и укажите имя для файла. Я указал MyData.plist
В Project Navigator нажмите правой кнопкой на только что созданном файле и выберите Open As -> Source Code.
Замените текст файла на следующий:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>SamsungDevice</key> <array> <string>Galaxy Phone</string> <string>Edge Phone</string> <string>Galaxy Tab</string> <string>Gear Watch</string> <string>Low Cost Phones</string> </array> <key>AppleDevice</key> <array> <string>iPhone</string> <string>Mac</string> <string>iPad</string> <string>MacBookPro</string> <string>Apple Watch</string> <string>Apple TV</string> </array> </dict> </plist>
Думаю, по тексту понятно, что в этом файле у нас есть словарь с двумя элементами, каждый из которых это ключ типа строка и массив строк в качестве значений. Если открыть это же файл средством просмотра plist-файлов (правой кнопкой по файлу MyData.plist -> Open As -> Property List), то можно будет увидеть структуру нашего файла в общем и данные массивов AppleDevice и SamsungDevice в частности.
Работа с файлами
Для начала добавьте outlet для хранения загруженных данных.
// // proSwift.ru // // Swift 3 var dictFromFile = NSDictionary()
Далее в методе, который вызывается при нажатии на кнопку загрузки напишите следующий код:
// // proSwift.ru // // Swift 3 @IBAction func loadData(_ sender: UIButton) { let path = Bundle.main.path(forResource: "MyData", ofType: "plist") if let path = path { dictFromFile = (NSDictionary(contentsOfFile: path))! dalaLabel.text = "Данные загружены" } }
Мы получаем путь для фала, который существует в нашей песочнице. Файловая система накладывает существенные ограничения на работу с файлами внутри проекта и полные ограничения для работы с файлами за пределами проекта. Поэтому нужно использовать методы такие как path(forResource:ofType:) для получения пути файла внутри песочницы.
Далее загруженные данные мы преобразуем в словарь методом класса NSDictionary (contentsOfFile:). После этих манипуляций мы меняем текст dalaLabel, чтобы отразить окончание процесса загрузки.
Вот и все. Две строки позволяют быстро загрузить данные из plist файла.
В итоге текст фалйа ViewController.swift у меня выглядит так:
// // proSwift.ru // // ViewController.swift // PlistToTable // // Created by Andrew Belozerov on 18.03.17. // Copyright © 2017 Andrew Belozerov. All rights reserved. // import UIKit class ViewController: UIViewController { var dictFromFile = NSDictionary() @IBOutlet weak var dalaLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } @IBAction func loadData(_ sender: UIButton) { let path = Bundle.main.path(forResource: "MyData", ofType: "plist") if let path = path { dictFromFile = (NSDictionary(contentsOfFile: path))! dalaLabel.text = "Данные загружены" } } @IBAction func showTable(_ sender: UIButton) { } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
Загрузка данных в таблицу
Ну и последний шаг это загрузка дынных из файла в таблицу.
Еще раз подумаем что же нам надо и чем мы располагаем.
У нас есть словарь который содержит два колюча и и два соответствующих массива строк. А для отображения таблицы нам нужен массив элементов в качестве источника данных. Мы уже разбирали как преобразовать коллекцию словарей в источник данных для таблицы с секциями в этой статье. Давайте воспользуемся методом, который был описан раньше.
Весь дополнительный функционал и соответсвенно код мы будем писать в файле DictTableViewController.swift.
Сразу после строки import UIKit Добавьте следующий код вспомогательной структуры:
// // proSwift.ru // // Swift 3 struct Objects { //Вспомогательная структура var sectionName : String! var sectionObjects : [String]! }
А уже внутри класса добавьте свойство с массивом объектов вспомогательной структуры. Следует также добавить свойство для получения и хранения данных из контролера с кнопками внутри класса с таблицей.
// // proSwift.ru // // Swift 3 var dataSource = [Objects]() // массив объектов вспомогательных структур var loadedData = NSDictionary() // в это свойство мы передадим данные при переходе segue
Самое время скомпилировать проект и посмотреть что у нас получилось. У меня проект выглядит так:
После нажатия на кнопку загрузки выполняется преобразование данных из файла меняется текст на экране. А если нажать на кнопку перехода к таблице, то откроется таблица, но на текущий момент она пустая.
Теперь мы убедились, что переход от главного экрана с кнопками к таблице работает, но нам нужно передать данные, полученные из файла. Для этого внутрь класса основного контроллера — ViewController, — добавим метод, который выполняется при переходе.
// // proSwift.ru // // Swift 3 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "ShowTableSegue" { let dtvc = segue.destination as! DictTableViewController dtvc.loadedData = dictFromFile } }
Как я писал ранее этот метод выполняется при любом переходе, но нам нужен именно наш переход ShowTableSegue, поэтому мы сначала проверили что это именно он, потом получили контроллер в который переходим, привели его к типу DictTableViewController и только после этого передали в этот контроллер в свойство для хранения данных наш загруженный plist-файл, преобразованный в словарь.
Следует проверить работоспособность нашего проекта. Для этого в метод viewDidLoad() класса DictTableViewController нужно добавить вывод в консоль содержимое свойства loadedData:
// // proSwift.ru // // Swift 3 override func viewDidLoad() { super.viewDidLoad() print(loadedData) }
После запуска проекта выполните загрузку данных и после появления сообщения о загрузке сделайте переход к таблице. Если все сделано верно, то в консоли появится следующий текст:
{ AppleDevice = ( iPhone, Mac, iPad, MacBookPro, "Apple Watch", "Apple TV" ); SamsungDevice = ( "Galaxy Phone", "Edge Phone", "Galaxy Tab", "Gear Watch", "Low Cost Phones" ); }
Теперь преобразуем полученные данные. Для этого добавим метод refractoring() в класс DictTableViewController.
// // proSwift.ru // // Swift 3 private func refractoring() { for (key , value) in loadedData { dataSource.append(Objects(sectionName: key as! String, sectionObjects: value as! [String])) } }
Как видим это метод берет словарь в свойстве loadedData и преобразует его массив источника данных dataSource. Нужно вызвать этот метод при загрузке контроллера, чтобы при его работе мы располагали всеми данными. Вставьте строку с вызовом метода refractoring() в метод viewDidLoad()
// // proSwift.ru // // Swift 3 override func viewDidLoad() { super.viewDidLoad() refractoring() print(loadedData) }
Чтобы не запутаться приведу код всего класса DictTableViewController на текущий момент:
// // proSwift.ru // // Swift 3 // // DictTableViewController.swift // PlistToTable // // Created by Andrew Belozerov on 18.03.17. // Copyright © 2017 Andrew Belozerov. All rights reserved. // import UIKit struct Objects { //Вспомогательная структура var sectionName : String! var sectionObjects : [String]! } class DictTableViewController: UITableViewController { var dataSource = [Objects]() var loadedData = NSDictionary() override func viewDidLoad() { super.viewDidLoad() refractoring() print(loadedData) } private func refractoring() { for (key , value) in loadedData { dataSource.append(Objects(sectionName: key as! String, sectionObjects: value as! [String])) } } }
Работа с таблицей
Для начала измените стиль таблицы, чтобы отображались заголовки секций. В нашем примере это будет необходимо. Как вы наверное уже догадались у нас в таблице будет две секции: AppleDevice и SamsungDevice, а втабличных частях этих секций — будут устройства из массива данных.
Давайте добавим методы работы с таблицами, используя в качестве источника данных массив dataSource
// // proSwift.ru // // Swift 3 // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return dataSource.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource[section].sectionObjects.count } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return dataSource[section].sectionName } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "DictCell", for: indexPath) cell.textLabel?.text = dataSource[indexPath.section].sectionObjects[indexPath.row] return cell }
Итак, сначала мы получили количество секций таблицы, следующим методом мы будем возвращать количество ячеек в каждой секции. Как видите это количество будет равно количеству устройств в массиве данных. Третий метод возвращает текстовый заголовок для секций и четвертый непосредственно ячейку таблицы.
Опять же, я не останавливаюсь на особенностях работы с таблицами в iOS, примеров и уроков куча. Но если что-то не понятно — можно задавать вопросы в комментариях — я с удовольствием отвечу.
Запустите проект, загрузите данные и выведите таблицу. У меня получилось вот так:
Вот и все. Мы загрузили данные из plist-файла, преобразовали данные для вывода в таблицу и вывели красивую структурированную таблицу на экран.
Полный код фалов
ViewController.swift:
// // proSwift.ru // // Swift 3 // // ViewController.swift // PlistToTable // // Created by Andrew Belozerov on 18.03.17. // Copyright © 2017 Andrew Belozerov. All rights reserved. // import UIKit class ViewController: UIViewController { var dictFromFile = NSDictionary() @IBOutlet weak var dalaLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } @IBAction func loadData(_ sender: UIButton) { let path = Bundle.main.path(forResource: "MyData", ofType: "plist") if let path = path { dictFromFile = (NSDictionary(contentsOfFile: path))! dalaLabel.text = "Данные загружены" } } @IBAction func showTable(_ sender: UIButton) { } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "ShowTableSegue" { let dtvc = segue.destination as! DictTableViewController dtvc.loadedData = dictFromFile } } }
DictTableViewController:
// // proSwift.ru // // Swift 3 // // DictTableViewController.swift // PlistToTable // // Created by Andrew Belozerov on 18.03.17. // Copyright © 2017 Andrew Belozerov. All rights reserved. // import UIKit struct Objects { //Вспомогательная структура var sectionName : String! var sectionObjects : [String]! } class DictTableViewController: UITableViewController { var dataSource = [Objects]() var loadedData = NSDictionary() override func viewDidLoad() { super.viewDidLoad() refractoring() print(loadedData) } private func refractoring() { for (key , value) in loadedData { dataSource.append(Objects(sectionName: key as! String, sectionObjects: value as! [String])) } } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return dataSource.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource[section].sectionObjects.count } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return dataSource[section].sectionName } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "DictCell", for: indexPath) cell.textLabel?.text = dataSource[indexPath.section].sectionObjects[indexPath.row] return cell } }
Ссылка проекта на GitHub
Спасибо за ваш ресурс, спасибо за старания! Очень полезная информация для начинающих в Xcode.
Спасибо! За труд, хороший урок!