I have class like this below to pick questions from plist, but now I want to take them in random order
class QuizLoader {
public func loadSimpleQuiz(forQuiz guizName: String) throws -> [SimpleQuestion] {
var questions = [SimpleQuestion]()
if let path = Bundle.main.path(forResource: guizName, ofType: "plist") {
if let dict = NSDictionary(contentsOfFile: path) {
let tempArray: Array = dict["Questions"]! as! [Dictionary<String,AnyObject>]
for dictionary in tempArray {
let questionToAdd = SimpleQuestion(question: dictionary["Question"] as! String, correctAnswer: dictionary["CorrectAnswer"] as! String)
}
return questions
} else {
throw LoaderError.dictionaryField
}
} else {
throw LoaderError.pathField
}
}
I was trying to do this with this hint but I have error with var unsignedArrayCount = UInt32(quoteDictionary.count)
I have already spend few hours trying to figure it out, but still no luck.
func loadQuestions() {
do {
questionArray = try quizeLoader.loadMultipleChoiceQuiz(forQuiz: "MultipleChoice")
loadNextQuestion()
} catch {
switch error {
case LoaderError.dictionaryField:
print("Could not load directory")
case LoaderError.pathField:
print("Cound not find valid file at path")
default:
print("Unknown error")
}
}
}
func loadNextQuestion() {
currentQuestion = questionArray[questionIndex]
setTitleForButtons()
}
Related
I'm trying to read a text file and return an array of dictionary in swift
the text file has the following data:
13582;Name 1;12345;5
13583;Name 2;23456;5
13585;Name 3;EX934;6
13598;Name 4;XE345_c;6
13600;Name 5;XF8765;6
func machineNumberToName() -> [[String: String]] {
var dic1 = [String: String]()
var dic2 = [String: String]()
var dic3 = [String: String]()
var dic4 = [String: String]()
// FileName for machines
let fileName = "Machines.txt";
if let path = Bundle.main.path(forResource: fileName, ofType: nil) {
do {
let contents = try! String(contentsOfFile: path)
let lines = contents.split(separator: "\n")
for line in lines {
var entries = lines.split(separator: ";")
dic1["machineNumber"] = entries[0]
dic2["machineName"] = entries[1]
dic3["machineXML"] = entries[2]
dic4["wifi"] = entries[3]
return [dic1, dic2, dic3, dic4]
}
} catch {
print(error.localizedDescription)
}
} else {
NSLog("file not found: \(fileName)")
return []
}
}
however I get the error
Cannot assign value of type 'Array<String.SubSequence>.SubSequence' (aka 'ArraySlice<Substring>') to subscript of type 'String'
Not sure what I'm doing wrong!
entries is not an array of String, it is an array of ArraySlice<Substring>, or informally an array of substrings.
You can use String(entries[0]) to get a string to put in your dictionary.
You have another problem though; You will only end up with the first line in the dictionaries, since you return out of the loop. Even if you fix that, returning an array of dictionaries is icky. Create an appropriate struct and return an array of those structs
struct MachineDetails {
let machineNumber: String
let machineName: String
let machineXML: String
let machineWiFi: String
}
func getMachineDetails() -> [MachineDetails] {
var details = [MachineDetails]()
let fileName = "Machines.txt";
if let path = Bundle.main.path(forResource: fileName, ofType: nil) {
do {
let contents = try String(contentsOfFile: path)
let lines = contents.split(separator: "\n")
for line in lines {
let entries = line.split(separator: ";").map { String($0) }
if entries.count == 4 {
let newMachine = MachineDetails(machineNumber:entries[0],
machineName:entries[1],
machineXML:entries[2],
machineWiFi:entries[3])
details.append(newMachine)
} else {
print("Malformed line \(line)")
}
}
} catch {
print(error.localizedDescription)
}
} else {
NSLog("file not found: \(fileName)")
}
return details
}
I need to create an array of Categories that contains Questions array.
struct CategoryFB {
var title: String
var id: Int
var questions: [QuestionsFB]
var dictionary: [String : Any] {
return ["title" : title,
"id" : id]
}
}
extension CategoryFB {
init?(dictionary: [String : Any], questions: [QuestionsFB]) {
guard let title = dictionary["title"] as? String, let id = dictionary["id"] as? Int else { return nil }
self.init(title: title, id: id, questions: questions)
}
}
Firestore has a following structure
Collection("Categories")
Document("some_id")
Collection("Questions")
How to create array like this?
array = [Category(title: "First",
questions: [
Question("1"),
...
]),
... ]
My try was wrong:
db.collection("Categories").order(by: "id", descending: false).getDocuments {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
for document in querySnapshot!.documents {
print(document.documentID)
self.db.collection("Categories").document(document.documentID).collection("Questions").getDocuments(completion: { (subQuerySnapshot, error) in
if error != nil {
print(error!.localizedDescription)
} else {
var questionsArray: [QuestionsFB]?
questionsArray = subQuerySnapshot?.documents.compactMap({QuestionsFB(dictionary: $0.data())})
self.categoriesArray = querySnapshot?.documents.compactMap({CategoryFB(dictionary: $0.data(), questions: questionsArray!)})
print(self.categoriesArray![0].questions.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
}
}
Your main problem seems to stem from the fact that you're regenerating your categories array every time you run your subquery, and when you do, you're only supplying a single questions array to the entire thing.
There's lots of ways to fix this. I would probably break it up so that you a) First allow yourself to create a category array without any questions, and then b) Go back through each of your individual subQueries and insert them into your categories as you get them.
Your final code might look something like this. Note that this would mean changing your Category object so that you can first create it without a Questions array, and implementing this custom addQuestions:toCategory: method (which would be a whole lot easier if you stored your categories as a dictionary instead of an array)
db.collection("Categories").order(by: "id", descending: false).getDocuments {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
self.categoriesArray = querySnapshot?.documents.compactMap({CategoryFB(dictionary: $0.data()})
for document in querySnapshot!.documents {
print(document.documentID)
self.db.collection("Categories").document(document.documentID).collection("Questions").getDocuments(completion: { (subQuerySnapshot, error) in
if error != nil {
print(error!.localizedDescription)
} else {
var questionsArray: [QuestionsFB]?
questionsArray = subQuerySnapshot?.documents.compactMap({QuestionsFB(dictionary: $0.data())})
self.addQuestions(questionsArray toCategory: document.documentID)
print(self.categoriesArray![0].questions.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
}
}
Alternately, if you think you're going to be in a situation where you're always going to want to grab your questions every time you want to grab a category, you might consider not putting them in a subcollection at all, and just making them a map in the original category document.
This is the solution which I found by myself. Hopefully this will help someone in the future.
func getData(completion: #escaping (_ result: [Any]) -> Void) {
let rootCollection = db.collection("Categories")
var data = [Any]()
rootCollection.order(by: "id", descending: false).getDocuments(completion: {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
guard let topSnapshot = querySnapshot?.documents else { return }
for category in topSnapshot {
rootCollection.document(category.documentID).collection("Questions").getDocuments(completion: {
(snapshot, err) in
guard let snapshot = snapshot?.documents else { return }
var questions = [Question]()
for document in snapshot {
let title = document.data()["title"] as! String
let details = document.data()["details"] as! String
let article = document.data()["article"] as! String
let link = document.data()["link"] as! String
let id = document.data()["id"] as! String
let possibleAnswers = document.data()["possibleAnswers"] as! [String]
let rightAnswerID = document.data()["rightAnswerID"] as! Int
let newQuestion = Question(title: title, article: article, details: details, link: link, possibleAnswers: possibleAnswers, rightAnswerID: rightAnswerID, id: id)
questions.append(newQuestion)
}
let categoryTitle = category.data()["title"] as! String
let collectionID = category.data()["id"] as! Int
let newCategory = Category(title: categoryTitle, id: collectionID, questions: questions)
data.append(newCategory)
//Return data on completion
completion(data)
})
}
}
})
}
I'm trying to save my json file and show it to offline. I'm trying this code but it is not working for me ..
let myData = NSKeyedArchiver.archivedData(withRootObject: self.data)
UserDefaults.standard.set(myData, forKey: "userJson")
UserDefaults.standard.synchronize()
Can any one suggest me better way to save data and show off line ?
You should not save JSON in the UserDefault, Instead save it in file in document directory
I have created generic class which allows to do it easily
//
// OfflineManager.swift
//
//
// Created by Prashant on 01/05/18.
// Copyright © 2018 Prashant. All rights reserved.
//
import UIKit
class OfflineManager: NSObject {
static let sharedManager = OfflineManager()
let LocalServiceCacheDownloadDir = "LocalData"
enum WSCacheKeys {
case CampignList
case CampignDetail(id:String)
case ScreenShotList
var value:String {
switch self {
case .CampignList:
return "CampignList"
case .CampignDetail(let id):
return id
case .ScreenShotList :
return "ScreenShotList"
}
}
}
func getBaseForCacheLocal(with fileName:String) -> String? {
let filePath = FileManager.default.getDocumentPath(forItemName: self.LocalServiceCacheDownloadDir)
if FileManager.default.directoryExists(atPath: filePath) {
return filePath.stringByAppendingPathComponent(fileName)
} else {
if FileManager.default.createDirectory(withFolderName: self.LocalServiceCacheDownloadDir) {
return filePath.stringByAppendingPathComponent(fileName)
}
}
return nil
}
//------------------------------------------------------------
#discardableResult
func cacheDataToLocal<T>(with Object:T,to key:WSCacheKeys) -> Bool {
let success = NSKeyedArchiver.archiveRootObject(Object, toFile: getBaseForCacheLocal(with: key.value)!)
if success {
print( "Local Data Cached\(String(describing: getBaseForCacheLocal(with: key.value)))")
} else {
print("Error")
}
return success
}
//------------------------------------------------------------
func loadCachedDataFromLocal<T>(with key:WSCacheKeys ) -> T? {
return NSKeyedUnarchiver.unarchiveObject(withFile: getBaseForCacheLocal(with: key.value)!) as? T
}
//------------------------------------------------------------
func removeAllCacheDirs () {
do {
try FileManager.default.removeItem(atPath: self.getBaseForCacheLocal(with: "")!)
} catch {
print("error in remove dir \(error.localizedDescription)")
}
}
//--------------------------------------------------------------------------------
}
Here is some helper methods of extension FileManager
public var getDocumentDirectoryPath: String {
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
return documentDirectory
}
public func getDocumentPath(forItemName name: String)-> String {
return getDocumentDirectoryPath.stringByAppendingPathComponent(name)
}
public func directoryExists(atPath filePath: String)-> Bool {
var isDir = ObjCBool(true)
return FileManager.default.fileExists(atPath: filePath, isDirectory: &isDir )
}
public func createDirectory(withFolderName name: String)-> Bool {
let finalPath = getDocumentDirectoryPath.stringByAppendingPathComponent(name)
return createDirectory(atPath: finalPath)
}
Here Is String extension's method
public func stringByAppendingPathComponent(_ path: String) -> String {
let fileUrl = URL.init(fileURLWithPath: self)
let filePath = fileUrl.appendingPathComponent(path).path
return filePath
}
How to use it ?
To save
OfflineManager.sharedManager.cacheDataToLocal(with: object as! [String:Any], to: .CampignList)
To read data
DispatchQueue.global().async {
// GET OFFLINE DATA
if let object:[String:Any] = OfflineManager.sharedManager.loadCachedDataFromLocal(with: .CampignList) {
do {
let data = try JSONSerialization.data(withJSONObject: object, options: [])
let object = try CampaignListResponse.init(data: data)
self.arrCampignList = object.data ?? []
DispatchQueue.main.async {
self.tableVIew.reloadData()
}
} catch {
}
}
}
Note: You can define your own WSCacheKeys for type of your json like i am fetching some campaign list
You can use Realm or CoraData for saving data and showing it when you are offline.
Here is the official link for Realm.You can learn from here.
https://realm.io/docs/swift/latest
I am trying to save/update a dictionary inside a plist file. If a try to save a single value into a plist I have no problem calling the dictionary as a NSMutableDictionary however, if I want to add another dictionary inside it I cannot achieve it.
For example I want to add a dictionary inside the plist with dictionary "dict2" below called "level2" having values as "value1", "value2", "value3" I cannot Achieve this.
Is there a way I can do this?
struct MyProgress {
//1
enum PlistError: ErrorType {
case FileNotWritten
case FileDoesNotExist
}
//2
let name:String
//3
var sourcePath:String? {
guard let path = NSBundle.mainBundle().pathForResource(name, ofType: "plist") else { return .None }
return path
}
//4
var destPath:String? {
guard sourcePath != .None else { return .None }
let dir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
return (dir as NSString).stringByAppendingPathComponent("\(name).plist")
}
init?(name:String) {
//1
self.name = name
//2
let fileManager = NSFileManager.defaultManager()
//3
guard let source = sourcePath else { return nil }
guard let destination = destPath else { return nil }
guard fileManager.fileExistsAtPath(source) else { return nil }
//4
if !fileManager.fileExistsAtPath(destination) {
//5
do {
try fileManager.copyItemAtPath(source, toPath: destination)
} catch let error as NSError {
print("Unable to copy file. ERROR: \(error.localizedDescription)")
return nil
}
}
}
//1
func getValuesInPlistFile() -> NSDictionary?{
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(destPath!) {
guard let dict = NSDictionary(contentsOfFile: destPath!) else { return .None }
return dict
} else {
return .None
}
}
//2
func getMutablePlistFile() -> NSMutableDictionary?{
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(destPath!) {
guard let dict = NSMutableDictionary(contentsOfFile: destPath!) else { return .None }
return dict
} else {
return .None
}
}
//3
func addValuesToPlistFile(dictionary:NSDictionary) throws {
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(destPath!) {
if !dictionary.writeToFile(destPath!, atomically: false) {
print("File not written successfully")
throw PlistError.FileNotWritten
}
} else {
throw PlistError.FileDoesNotExist
}
}
}
class ProgressFunctions {
static func saveProgressData (plistName: String, dictName:String, dataName:String, dataValue: AnyObject) {
if let myProgressPlist = MyProgress(name: plistName) {
let dict = myProgressPlist.getMutablePlistFile()!
//let savingDict = dict[dictName]![dataName]!
//dict[dictName] = dataValue
print(dict)
do {
try myProgressPlist.addValuesToPlistFile(dict)
} catch {
print(error)
}
}
}
}
I have aded HMSegmentedControl to make a swiping segmented control in my iOS app.I am loading all the data initially because then it will facilitate the scrolling. So I have to load several tables under several categories. Category name is the segmented control item title. So this is how I set my titles.
for(var i=0; i<dm.TableData.count; i++)
{
self.array.append(dm.TableData[i]["name"] as! String)
}
segmentedControl.sectionTitles=self.array
Categories are loading according to the order of this array without any issue. Then I am loading my tables like this.
for i in 0..<dm.TableData.count {
self.catID=self.dm.TableData[i]["term_id"] as? String
switch self.catID {
case "55":
self.jsonParser()
case "1":
self.getBusiness()
case "2":
self.getNews()
case "4":
self.getSports()
case "5":
self.getEntertainment()
case "57":
self.getCrime()
case "21":
self.getPolitics()
case "28":
self.getWorld()
case "89":
self.getVideos()
case "111":
self.getLocalNews()
default:
print("Default")
}
}
This is my jsonParser method. getBusiness(),getNews(),getSports() all those methods are just same as this and load to seperate array and the dictionary key is different.
func jsonParser() {
let urlPath = "http://www.li67t8.lk/mobileapp/news.php?"
let category_id=self.catID
let catParam="category_id"
let strCatID="\(catParam)=\(category_id)"
let strStartRec:String=String(startRec)
let startRecPAram="start_record_index"
let strStartRecFull="\(startRecPAram)=\(strStartRec)"
let strNumOfRecFull="no_of_records=10"
let fullURL = "\(urlPath)\(strCatID)&\(strStartRecFull)&\(strNumOfRecFull)"
print(fullURL)
guard let endpoint = NSURL(string: fullURL) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(URL:endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
print(json)
if let countries_list = json["data"] as? NSArray
{
// for (var i = 0; i < countries_list.count ; i++ )
for i in 0..<countries_list.count
{
if let country_obj = countries_list[i] as? NSDictionary
{
//self.TableData.append(country_obj)
self.breakingNews.append(country_obj)
}
}
dispatch_async(dispatch_get_main_queue()) {
print("%%%%%%%%%%% CAT ID %%%%%%%%%% \(self.catID)")
if let checkedUrl = NSURL(string: self.breakingNews[0]["thumb_url"] as! String) {
self.imageURL=checkedUrl
}
if let time = self.breakingNews[0]["duration"] as? String
{
self.timeDuration=time
}
if let likes = self.breakingNews[0]["read_count"] as? String
{
self.noOfLikes=likes
}
if let title = self.breakingNews[0]["post_title"] as? String
{
self.titleNews=title
}
self.addedArray.append("Breaking News")
self.commonData["Breaking News"]=self.breakingNews
self.updateUI()
print("-------BREAKING--------")
}
}
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
I have one method for UpdateUI() and it creates UITableView dynamically and assign tag value dynamically (I keep an Int called index and I assign that index to tableview tag and after adding table to super view I increment the index count by 1)
According to this I get data and load to the tableview. But my problem is data not getting in the same order I call to those methods. As an example jsonParser() returns its data set and then it returns getSportsData() data. like wise my data not according to the segment title order.
So how can I solve this problem? Please help me.
Thanks