Я играю с новым языком программирования Swift Apple и имею некоторые проблемы...
В настоящее время я пытаюсь прочитать файл plist, в Objective-C я бы сделал следующее, чтобы получить содержимое как NSDictionary:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Config" ofType:@"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:filePath];
Как получить plist как словарь в Swift?
Я предполагаю, что могу получить путь к plist с помощью:
let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist")
Когда это работает (если это правильно?): Как получить контент в виде словаря?
Также более общий вопрос:
Можно ли использовать классы NS * по умолчанию? Я так думаю... или я что-то упускаю? Насколько я знаю, рамки по умолчанию NS * по-прежнему действительны и подходят для использования?
Вы все еще можете использовать NSDictionaries в Swift:
Для Swift 4
var nsDictionary: NSDictionary?
if let path = Bundle.main.path(forResource: "Config", ofType: "plist") {
nsDictionary = NSDictionary(contentsOfFile: path)
}
Для Свифта 3+
if let path = Bundle.main.path(forResource: "Config", ofType: "plist"),
let myDict = NSDictionary(contentsOfFile: path){
// Use your myDict here
}
И старые версии Swift
var myDict: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist") {
myDict = NSDictionary(contentsOfFile: path)
}
if let dict = myDict {
// Use your dict here
}
Классы NSC по-прежнему доступны и отлично подходят для использования в Swift. Я думаю, что они, вероятно, скоро захотят сместить фокус на swift, но в настоящее время быстрые API не обладают всей функциональностью базовых NSClasses.
Это то, что я делаю, если я хочу преобразовать .plist в словарь Swift:
if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist") {
if let dict = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
// use swift dictionary as normal
}
}
Отредактировано для Swift 2.0:
if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist"), dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
// use swift dictionary as normal
}
Отредактировано для Swift 3.0:
if let path = Bundle.main.path(forResource: "Config", ofType: "plist"), let dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
// use swift dictionary as normal
}
В swift 3.0 Чтение из Plist.
func readPropertyList() {
var propertyListForamt = PropertyListSerialization.PropertyListFormat.xml //Format of the Property List.
var plistData: [String: AnyObject] = [:] //Our data
let plistPath: String? = Bundle.main.path(forResource: "data", ofType: "plist")! //the path of the data
let plistXML = FileManager.default.contents(atPath: plistPath!)!
do {//convert the data to a dictionary and handle errors.
plistData = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListForamt) as! [String:AnyObject]
} catch {
print("Error reading plist: \(error), format: \(propertyListForamt)")
}
}
Подробнее КАК ИСПОЛЬЗОВАТЬ СПИСКИ НЕДВИЖИМОСТИ (.PLIST) В SWIFT.
Теперь вы можете использовать Decodable протокол для декодирования .plist в пользовательскую структуру. Я рассмотрю простой пример для более сложных структур .plist, которые я рекомендую прочитать в Decodable/Encodable (хороший ресурс здесь: https://benscheirman.com/2017/06/swift-json/).
Сначала настройте вашу структуру в формате вашего файла .plist. Для этого примера я рассмотрю .plist с словарём корневого уровня и 3 записями: 1 строка с ключом "name", 1 Int с ключом "age" и 1 Boolean с ключом "single". Вот структура:
struct Config: Decodable {
private enum CodingKeys: String, CodingKey {
case name, age, single
}
let name: String
let age: Int
let single: Bool
}
Достаточно просто. Теперь классная часть. Используя класс PropertyListDecoder, мы можем легко проанализировать файл .plist для создания этой структуры:
func parseConfig() -> Config {
let url = Bundle.main.url(forResource: "Config", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let decoder = PropertyListDecoder()
return try! decoder.decode(Config.self, from: data)
}
Не намного больше кода для беспокойства, и все это в Swift. Более того, теперь у нас есть экземпляр структуры Config, которую мы можем легко использовать:
let config = parseConfig()
print(config.name)
print(config.age)
print(config.single)
Это печатает значение для ключей "name", "age" и "single" в .plist.
Bundle.main.url(forResource: "Config", withExtension: "plist")
и избавиться от URL(fileURLWithPath
? И как файл должен существовать (при проектировании / компиляции) время) все значения могут быть развернуты принудительно. Код не должен аварийно завершать работу, если все спроектировано правильно.
url(forResource: "Config", withExtension: "plist")
Я просто сопоставляю то, что OP сделал в их коде, для сравнения. Что касается силы, разворачивающей все, я стараюсь ошибиться в сторону осторожности. Я думаю, что это основной вопрос для Swift в целом. Я предпочел бы точно знать, что мой код будет делать в любой ситуации, чем сбой.
В этом ответе используются собственные объекты Swift, а не NSDictionary.
//get the path of the plist file
guard let plistPath = Bundle.main.path(forResource: "level1", ofType: "plist") else { return }
//load the plist as data in memory
guard let plistData = FileManager.default.contents(atPath: plistPath) else { return }
//use the format of a property list (xml)
var format = PropertyListSerialization.PropertyListFormat.xml
//convert the plist data to a Swift Dictionary
guard let plistDict = try! PropertyListSerialization.propertyList(from: plistData, options: .mutableContainersAndLeaves, format: &format) as? [String : AnyObject] else { return }
//access the values in the dictionary
if let value = plistDict["aKey"] as? String {
//do something with your value
print(value)
}
//you can also use the coalesce operator to handle possible nil values
var myValue = plistDict["aKey"] ?? ""
Я работал с Swift 3.0 и хотел дать ответ на обновленный синтаксис. Кроме того, и, что еще важнее, я использую объект PropertyListSerialization для выполнения тяжелого подъема, который намного более гибкий, чем просто использование NSDictionary, поскольку он позволяет массиву как корневому типу plist.
Ниже приведен скриншот plist, который я использую. Это немного сложно, чтобы показать доступную мощность, но это будет работать для любой допустимой комбинации типов plist.
Как вы можете видеть, я использую Array из String: String dictionaries для хранения списка имен веб-сайтов и их соответствующего URL-адреса.
Я использую объект PropertyListSerialization, как упоминалось выше, для тяжелого подъема для меня. Кроме того, Swift 3.0 стал более "Swifty", поэтому все имена объектов потеряли префикс "NS".
let path = Bundle.main().pathForResource("DefaultSiteList", ofType: "plist")!
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil)
После выполнения приведенного выше кода plist
будет иметь тип Array<AnyObject>
, но мы знаем, какой тип это действительно так, поэтому мы можем применить его к правильному типу:
let dictArray = plist as! [[String:String]]
// [[String:String]] is equivalent to Array< Dictionary<String, String> >
Теперь мы можем получить доступ к различным свойствам нашего массива String: String Dictionaries естественным образом. Надеемся, что они преобразуют их в реальные строго типизированные структуры или классы;)
print(dictArray[0]["Name"])
Swift 2.0: доступ к Info.Plist
У меня есть словарь с именем CoachMarksDictionary с логическим значением в Info.Plist. Я хочу получить доступ к значению bool и сделать его истинным.
let path = NSBundle.mainBundle().pathForResource("Info", ofType: "plist")!
let dict = NSDictionary(contentsOfFile: path) as! [String: AnyObject]
if let CoachMarksDict = dict["CoachMarksDictionary"] {
print("Info.plist : \(CoachMarksDict)")
var dashC = CoachMarksDict["DashBoardCompleted"] as! Bool
print("DashBoardCompleted state :\(dashC) ")
}
Запись в Plist:
Из пользовательского Plist: - (Make from File-New-File-Resource-PropertyList. Добавлено три строки с именем: DashBoard_New, DashBoard_Draft, DashBoard_Completed)
func writeToCoachMarksPlist(status:String?,keyName:String?)
{
let path1 = NSBundle.mainBundle().pathForResource("CoachMarks", ofType: "plist")
let coachMarksDICT = NSMutableDictionary(contentsOfFile: path1!)! as NSMutableDictionary
var coachMarksMine = coachMarksDICT.objectForKey(keyName!)
coachMarksMine = status
coachMarksDICT.setValue(status, forKey: keyName!)
coachMarksDICT.writeToFile(path1!, atomically: true)
}
Этот метод можно назвать
self.writeToCoachMarksPlist(" true - means user has checked the marks",keyName: "the key in the CoachMarks dictionary").
Swift - Чтение/запись plist и текстового файла....
override func viewDidLoad() {
super.viewDidLoad()
let fileManager = (NSFileManager .defaultManager())
let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if (directorys != nil){
let directories:[String] = directorys!;
let dictionary = directories[0]; //documents directory
// Create and insert the data into the Plist file ....
let plistfile = "myPlist.plist"
var myDictionary: NSMutableDictionary = ["Content": "This is a sample Plist file ........."]
let plistpath = dictionary.stringByAppendingPathComponent(plistfile);
if !fileManager .fileExistsAtPath(plistpath){//writing Plist file
myDictionary.writeToFile(plistpath, atomically: false)
}
else{ //Reading Plist file
println("Plist file found")
let resultDictionary = NSMutableDictionary(contentsOfFile: plistpath)
println(resultDictionary?.description)
}
// Create and insert the data into the Text file ....
let textfile = "myText.txt"
let sampleText = "This is a sample text file ......... "
let textpath = dictionary.stringByAppendingPathComponent(textfile);
if !fileManager .fileExistsAtPath(textpath){//writing text file
sampleText.writeToFile(textpath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
} else{
//Reading text file
let reulttext = String(contentsOfFile: textpath, encoding: NSUTF8StringEncoding, error: nil)
println(reulttext)
}
}
else {
println("directory is empty")
}
}
Лучше всего использовать родные словари и массивы, потому что они были оптимизированы для использования с быстрым. Говоря, что вы можете быстро использовать классы NS..., и я думаю, что эта ситуация требует этого. Вот как вы его реализуете:
var path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist")
var dict = NSDictionary(contentsOfFile: path)
До сих пор (на мой взгляд) это самый простой и эффективный способ доступа к plist, но в будущем я ожидаю, что Apple добавит больше функциональности (например, используя plist) в родные словари.
Конвертируется в расширение удобства через ответ Ник:
extension Dictionary {
static func contentsOf(path: URL) -> Dictionary<String, AnyObject> {
let data = try! Data(contentsOf: path)
let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil)
return plist as! [String: AnyObject]
}
}
использование:
let path = Bundle.main.path(forResource: "plistName", ofType: "plist")!
let url = URL(fileURLWithPath: path)
let dict = Dictionary<String, AnyObject>.contentsOf(path: url)
Я готов поспорить, что он также будет работать для создания аналогичного расширения для массивов
Вы можете читать plist на языке SWIFT таким образом:
let path = NSBundle.mainBundle().pathForResource("PriceList", ofType: "plist")
let dict = NSDictionary(contentsOfFile: path)
Чтение значения одиночного словаря:
let test: AnyObject = dict.objectForKey("index1")
Если вы хотите получить полный многомерный словарь в plist:
let value: AnyObject = dict.objectForKey("index2").objectForKey("date")
Вот plist:
<plist version="1.0">
<dict>
<key>index2</key>
<dict>
<key>date</key>
<string>20140610</string>
<key>amount</key>
<string>110</string>
</dict>
<key>index1</key>
<dict>
<key>amount</key>
<string>125</string>
<key>date</key>
<string>20140212</string>
</dict>
</dict>
</plist>
может действительно сделать это в 1 строке
var dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Config", ofType: "plist"))
Я создал простой инициализатор Dictionary
который заменяет NSDictionary(contentsOfFile: path)
. Просто удали NS
.
extension Dictionary where Key == String, Value == Any {
public init?(contentsOfFile path: String) {
let url = URL(fileURLWithPath: path)
self.init(contentsOfURL: url)
}
public init?(contentsOfURL url: URL) {
guard let data = try? Data(contentsOf: url),
let dictionary = (try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any]) ?? nil
else { return nil }
self = dictionary
}
}
Вы можете использовать это так:
let filePath = Bundle.main.path(forResource: "Preferences", ofType: "plist")!
let preferences = Dictionary(contentsOfFile: filePath)!
UserDefaults.standard.register(defaults: preferences)
Swift 3.0
if let path = Bundle.main.path(forResource: "config", ofType: "plist") {
let dict = NSDictionary(contentsOfFile: path)
// use dictionary
}
Самый простой способ сделать это, на мой взгляд.
Вот немного более короткая версия, основанная на ответе @connor
guard let path = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist"),
let myDict = NSDictionary(contentsOfFile: path) else {
return nil
}
let value = dict.value(forKey: "CLIENT_ID") as! String?
в моем случае я создаю NSDictionary
, называемый appSettings
, и добавьте все необходимые ключи. Для этого случая решение:
if let dict = NSBundle.mainBundle().objectForInfoDictionaryKey("appSettings") {
if let configAppToken = dict["myKeyInsideAppSettings"] as? String {
}
}
objectForInfoDictionaryKey
был именно тем, что я искал.
Вы можете использовать это, я создаю простое расширение для словаря в github https://github.com/DaRkD0G/LoadExtension
extension Dictionary {
/**
Load a Plist file from the app bundle into a new dictionary
:param: File name
:return: Dictionary<String, AnyObject>?
*/
static func loadPlistFromProject(filename: String) -> Dictionary<String, AnyObject>? {
if let path = NSBundle.mainBundle().pathForResource("GameParam", ofType: "plist") {
return NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject>
}
println("Could not find file: \(filename)")
return nil
}
}
И вы можете использовать это для загрузки
/**
Example function for load Files Plist
:param: Name File Plist
*/
func loadPlist(filename: String) -> ExampleClass? {
if let dictionary = Dictionary<String, AnyObject>.loadPlistFromProject(filename) {
let stringValue = (dictionary["name"] as NSString)
let intergerValue = (dictionary["score"] as NSString).integerValue
let doubleValue = (dictionary["transition"] as NSString).doubleValue
return ExampleClass(stringValue: stringValue, intergerValue: intergerValue, doubleValue: doubleValue)
}
return nil
}
Шаг 1: простой и быстрый способ синтаксического разбора plist в режиме быстрого 3 +
extension Bundle {
func parsePlist(ofName name: String) -> [String: AnyObject]? {
// check if plist data available
guard let plistURL = Bundle.main.url(forResource: name, withExtension: "plist"),
let data = try? Data(contentsOf: plistURL)
else {
return nil
}
// parse plist into [String: Anyobject]
guard let plistDictionary = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: AnyObject] else {
return nil
}
return plistDictionary
}
}
Шаг 2: Как использовать:
Bundle().parsePlist(ofName: "Your-Plist-Name")
Поскольку этого ответа еще нет, просто хотел бы отметить, что вы также можете использовать свойство infoDictionary для получения информационного списка в виде словаря Bundle.main.infoDictionary
.
Хотя что-то вроде Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String)
может быть быстрее, если вас интересует только конкретный элемент в информационном списке.
// Swift 4
// Getting info plist as a dictionary
let dictionary = Bundle.main.infoDictionary
// Getting the app display name from the info plist
Bundle.main.infoDictionary?[kCFBundleNameKey as String]
// Getting the app display name from the info plist (another way)
Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String)
Мне нравятся все эти примеры кода, но кажется, что все они показывают, находится ли plist в самом приложении. Что делать, если вы пытаетесь проанализировать список, который находится в самой системе? Пример, если вы хотите проанализировать файл plist, расположенный в /System/Library/Extensions/xxx.kext/Contents/Info.plist? У кого-нибудь есть пример, как разобрать лист в этом сенарио?
Проанализированный список Swift 4.0 для iOS 11.2.6 и код для его анализа основаны на ответе https://stackoverflow.com/users/3647770/ashok-r выше.
<?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">
<array>
<dict>
<key>identity</key>
<string>blah-1</string>
<key>major</key>
<string>1</string>
<key>minor</key>
<string>1</string>
<key>uuid</key>
<string>f45321</string>
<key>web</key>
<string>http://web</string>
</dict>
<dict>
<key>identity</key>
<string></string>
<key>major</key>
<string></string>
<key>minor</key>
<string></string>
<key>uuid</key>
<string></string>
<key>web</key>
<string></string>
</dict>
</array>
</plist>
do {
let plistXML = try Data(contentsOf: url)
var plistData: [[String: AnyObject]] = [[:]]
var propertyListForamt = PropertyListSerialization.PropertyListFormat.xml
do {
plistData = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListForamt) as! [[String:AnyObject]]
} catch {
print("Error reading plist: \(error), format: \(propertyListForamt)")
}
} catch {
print("error no upload")
}
если вы хотите прочитать "двумерный массив" из .plist, вы можете попробовать его следующим образом:
if let path = Bundle.main.path(forResource: "Info", ofType: "plist") {
if let dimension1 = NSDictionary(contentsOfFile: path) {
if let dimension2 = dimension1["key"] as? [String] {
destination_array = dimension2
}
}
}
Plist - это простое перечисление Swift, которое я сделал для работы с списками свойств.
// load an applications info.plist data
let info = Plist(NSBundle.mainBundle().infoDictionary)
let identifier = info["CFBundleIndentifier"].string!
Другие примеры:
import Plist
// initialize using an NSDictionary
// and retrieve keyed values
let info = Plist(dict)
let name = info["name"].string ?? ""
let age = info["age"].int ?? 0
// initialize using an NSArray
// and retrieve indexed values
let info = Plist(array)
let itemAtIndex0 = info[0].value
// utility initiaizer to load a plist file at specified path
let info = Plist(path: "path_to_plist_file")
// we support index chaining - you can get to a dictionary from an array via
// a dictionary and so on
// don't worry, the following will not fail with errors in case
// the index path is invalid
if let complicatedAccessOfSomeStringValueOfInterest = info["dictKey"][10]["anotherKey"].string {
// do something
}
else {
// data cannot be indexed
}
// you can also re-use parts of a plist data structure
let info = Plist(...)
let firstSection = info["Sections"][0]["SectionData"]
let sectionKey = firstSection["key"].string!
let sectionSecret = firstSection["secret"].int!
Plist.swift
Сам Plist довольно прост, здесь его перечисление на случай, если вы обратитесь напрямую.
//
// Plist.swift
//
import Foundation
public enum Plist {
case dictionary(NSDictionary)
case Array(NSArray)
case Value(Any)
case none
public init(_ dict: NSDictionary) {
self = .dictionary(dict)
}
public init(_ array: NSArray) {
self = .Array(array)
}
public init(_ value: Any?) {
self = Plist.wrap(value)
}
}
// MARK:- initialize from a path
extension Plist {
public init(path: String) {
if let dict = NSDictionary(contentsOfFile: path) {
self = .dictionary(dict)
}
else if let array = NSArray(contentsOfFile: path) {
self = .Array(array)
}
else {
self = .none
}
}
}
// MARK:- private helpers
extension Plist {
/// wraps a given object to a Plist
fileprivate static func wrap(_ object: Any?) -> Plist {
if let dict = object as? NSDictionary {
return .dictionary(dict)
}
if let array = object as? NSArray {
return .Array(array)
}
if let value = object {
return .Value(value)
}
return .none
}
/// tries to cast to an optional T
fileprivate func cast<T>() -> T? {
switch self {
case let .Value(value):
return value as? T
default:
return nil
}
}
}
// MARK:- subscripting
extension Plist {
/// index a dictionary
public subscript(key: String) -> Plist {
switch self {
case let .dictionary(dict):
let v = dict.object(forKey: key)
return Plist.wrap(v)
default:
return .none
}
}
/// index an array
public subscript(index: Int) -> Plist {
switch self {
case let .Array(array):
if index >= 0 && index < array.count {
return Plist.wrap(array[index])
}
return .none
default:
return .none
}
}
}
// MARK:- Value extraction
extension Plist {
public var string: String? { return cast() }
public var int: Int? { return cast() }
public var double: Double? { return cast() }
public var float: Float? { return cast() }
public var date: Date? { return cast() }
public var data: Data? { return cast() }
public var number: NSNumber? { return cast() }
public var bool: Bool? { return cast() }
// unwraps and returns the underlying value
public var value: Any? {
switch self {
case let .Value(value):
return value
case let .dictionary(dict):
return dict
case let .Array(array):
return array
case .none:
return nil
}
}
// returns the underlying array
public var array: NSArray? {
switch self {
case let .Array(array):
return array
default:
return nil
}
}
// returns the underlying dictionary
public var dict: NSDictionary? {
switch self {
case let .dictionary(dict):
return dict
default:
return nil
}
}
}
// MARK:- CustomStringConvertible
extension Plist : CustomStringConvertible {
public var description:String {
switch self {
case let .Array(array): return "(array \(array))"
case let .dictionary(dict): return "(dict \(dict))"
case let .Value(value): return "(value \(value))"
case .none: return "(none)"
}
}
}
Я использую быстрые словари, но конвертирую их в и из NSDictionaries в свой класс файлового менеджера, например:
func writePlist(fileName:String, myDict:Dictionary<String, AnyObject>){
let docsDir:String = dirPaths[0] as String
let docPath = docsDir + "/" + fileName
let thisDict = myDict as NSDictionary
if(thisDict.writeToFile(docPath, atomically: true)){
NSLog("success")
} else {
NSLog("failure")
}
}
func getPlist(fileName:String)->Dictionary<String, AnyObject>{
let docsDir:String = dirPaths[0] as String
let docPath = docsDir + "/" + fileName
let thisDict = NSDictionary(contentsOfFile: docPath)
return thisDict! as! Dictionary<String, AnyObject>
}
Это кажется наименее тревожным способом читать и писать, но пусть остальная часть моего кода будет как можно быстрее.
Вот решение, которое я нашел:
let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
let test: AnyObject = levelBlocks.objectForKey("Level1")
println(test) // Prints the value of test
Я устанавливаю тип test
в AnyObject
, чтобы отключить предупреждение о неожиданном выводе, который может произойти.
Кроме того, это должно быть сделано в методе класса.
Для доступа и сохранения определенного значения известного типа:
let value = levelBlocks.objectForKey("Level1").objectForKey("amount") as Int
println(toString(value)) // Converts value to String and prints it
Простая структура для доступа к файлу plist (Swift 2.0)
struct Configuration {
static let path = NSBundle.mainBundle().pathForResource("Info", ofType: "plist")!
static let dict = NSDictionary(contentsOfFile: path) as! [String: AnyObject]
static let someValue = dict["someKey"] as! String
}
Использование:
print("someValue = \(Configuration.someValue)")