Swift Playground Ipad Saving input data and restore when needed - ipad

Below codes works on Ipad playground, but I want to save input data and later retrieve it. For example the following inputs:
let xGuardtar = askForNumber()
let irpf = askForDecimal()
let brutox = bruto as NSNumber
The other problem is that it should be placed at the end of the code, but what to do, when i would like to retrieve it when the app is opened, and a file exists!!, it will wait until all inputs are gathered again....ps don’t want to use a button.
Looked everywhere but found nothing....thanks in advance (> swift 4)
Code that works:
// Additional save data
struct Note: Codable {
let title: String
let text: String
let timestamp: Date
}
let note1 = Note(title: "Note One", text: "This is a sample note.", timestamp: Date())
let note2 = Note(title: "Note Two", text: "This is another sample note.", timestamp: Date())
let note3 = Note(title: "Note Three", text: "This is yet another sample note.", timestamp: Date())
let notes = [note1, note2, note3]
let documentsDirectory = FileManager.default.urls(for: .documentDirectory,in: .userDomainMask).first!
let archiveURL = documentsDirectory.appendingPathComponent("notes_test").appendingPathExten. . sion("plist")
let propertyListEncoder = PropertyListEncoder()
let encodedNotes = try? propertyListEncoder.encode(notes)
try? encodedNotes?.write(to: archiveURL,options: .noFileProtection)
// Comments retrieve data
let propertyListDecoder = PropertyListDecoder()
if let retrievedNotesData = try? Data(contentsOf: archiveURL),
let decodedNotes = try?
propertyListDecoder.decode(Array<Note>.self, from:
retrievedNotesData) {
print(decodedNotes)
}

Related

How to archive data in swift?

I am trying to archive data and want to store it in userdefault but app getting crash.
Also tried this
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: selectedPoductDetails, requiringSecureCoding: false)
selectedPoductDetails is dict of type [String: SelectedProductDetail]
import Foundation
class SelectedProductDetail {
let product: String
var amount: Double
var title: String
init(product: String, amount: Double, title: String ) {
self.product = product
self.amount = amount
self.title = title
}
}
May i know why its not working and possible solution for same?
For this case you can use UserDefaults
struct ProductDetail: Codable {
//...
}
let encoder = JSONEncoder()
let selectedProductDetails = ProductDetail()
// Set
if let data = try? encoder.encode(selectedProductDetails) {
UserDefaults.standard.set(data, forKey: "selectedProductDetails")
}
// Get
if let selectedProductDetailsData = UserDefaults.standard.object(forKey: "selectedProductDetails") as? Data {
let selectedProductDetails = try? JSONDecoder().decode(ProductDetail.self, from: selectedProductDetailsData)
}
As mentioned in the comments to use NSKeyedArchiver the class must adopt NSSecureCoding and implement the two required methods.
The types in your class are JSON compatible, so adopt Codable and archive the data with JSONEncoder (or PropertyListEncoder). You could even use a struct and delete the init method
struct SelectedProductDetail: Codable {
let product: String
var amount: Double
var title: String
}
var productDetails = [String: SelectedProductDetail]()
// populate the dictionary
do {
let data = try JSONEncoder().encode(productDetails)
UserDefaults.standard.set(data, forKey: "productDetails")
} catch {
print(error)
}
And load it
do {
guard let data = UserDefaults.standard.data(forKey: "productDetails") else { return }
productDetails = try JSONDecoder().decode([String: SelectedProductDetail].self, from: data)
} catch {
print(error)
}
Note:
UserDefaults is the wrong place for user data. It's better to save the data in the Documents folder

JSON Decoding error pops up, dunno what else to try

guys, I would really appreciate some help with JSON decoding. I am trying get API from this link: http://newsapi.org/v2/top-headlines?apiKey=a16b15f863454928804e218705d0f019+&country=us
I might have made some really amateur mistakes. First time uploading a problem here. TY for help!
Here is my DataManager
protocol NewsManagerDelegate {
func didUpdateNews(news: NewsModel)
}
import Foundation
struct NewsManager{
var delegate: NewsManagerDelegate?
let url = "https://newsapi.org/v2/top-headlines?apiKey=a16b15f863454928804e218705d0f019"
func fetchNews(_ countryName: String){
let newsUrlString = "\(url)+&country=\(countryName)"
performRequest(newsUrlString)
}
func performRequest(_ urlString: String){
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print("networking error \(error!)")
return
}
if let safeData = data{
if let news = parseJSON(safeData){
delegate?.didUpdateNews(news: news)
}
}
}
task.resume()
}
}
func parseJSON(_ newsData: Data) -> NewsModel?{
do{
let decodedData = try JSONDecoder().decode(NewsData.self, from: newsData)
let sourceName = decodedData.articles[5].source.name
let titleName = decodedData.articles[5].title
let linkToImage = decodedData.articles[5].urlToImage
let news = NewsModel(sourceName: sourceName, titleName: titleName, linkToImage: linkToImage )
return news
}catch{
print(error)
return nil
}
}
}
and my Data
import Foundation
struct NewsData: Codable {
let totalResults: Int
let articles: [Articles]
}
struct Articles: Codable{
let author: String?
let title: String
let description: String
let urlToImage: String
let source: Source
}
struct Source: Codable{
let name: String
}
I am receiving this error
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "articles", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "description", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
I tried to make some of the constants optional but after that no info would show up at all.
I would just change the property name to articleDescription and make it optional. If you would like to parse the publishedAt date all you need is to set decoder dateDecodingStrategy property to .iso8601. Besides that you are not constructing your url correctly. You should always use URLComponents when composing your url and change your urls properties types from String to URL:
struct Root: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title: String
let articleDescription: String?
let url, urlToImage: URL
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case source, author, title, articleDescription = "description", url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
Playground testing:
func fetchNews(_ countryCode: String) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "newsapi.org"
urlComponents.path = "/v2/top-headlines"
urlComponents.queryItems = [.init(name: "apiKey", value: "a16b15f863454928804e218705d0f019"),
.init(name:"country", value: countryCode)]
if let url = urlComponents.url {
performRequest(url)
}
}
func performRequest(_ url: URL) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("networking error", error ?? "nil")
return
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let root = try decoder.decode(Root.self, from: data)
let articles = root.articles
for article in articles {
print("article:", article, terminator: "\n")
}
} catch {
print(error)
}
}.resume()
}
fetchNews("us")
This will print:
article: Article(source: __lldb_expr_111.Source(id: Optional("cnn"), name: "CNN"), author: Optional("Oliver Darcy, CNN Business"), title: "Tucker Carlson backlash tells us something important about some Trump supporters - CNN", articleDescription: nil, url: https://www.cnn.com/2020/11/21/media/tucker-carlson-fox-news-traitor/index.html, urlToImage: https://cdn.cnn.com/cnnnext/dam/assets/201105014450-tucker-carlson-fox-news-presidential-election-fraud-super-tease.jpg, publishedAt: 2020-11-21 16:11:00 +0000, content: nil)
article: Article(source: __lldb_expr_111.Source(id: Optional("usa-today"), name: "USA Today"), author: Optional("Joel Shannon, Grace Hauck"), title: "Coronavirus updates: Donald Trump Jr. tests positive; model estimates 471k US deaths by March; Cuomo to receive International Emmy - USA TODAY", articleDescription: Optional("Donald Trump Jr. tests positive for the coronavirus. Model predicts more deaths. Thanksgiving during a pandemic happened before. Latest COVID news."), url: https://www.usatoday.com/story/news/health/2020/11/21/covid-news-donald-trump-jr-positive-thanksgiving-travel-not-advised/6367181002/, urlToImage: https://www.gannett-cdn.com/presto/2020/11/21/NSTT/caa3ba8a-8e3c-4b49-82e2-2810caf33d05-Testing3.jpg?crop=1574,886,x0,y80&width=1600&height=800&fit=bounds, publishedAt: 2020-11-21 15:56:15 +0000, content: Optional("A coronavirus vaccine might not be widely available until several months into 2021.\r\nUSA TODAY\r\nThe U.S reported a record high of more than 195,000 new daily cases of COVID-19 Friday, the same week t… [+11662 chars]"))
article: Article(source: __lldb_expr_111.Source(id: nil, name: "CBS Sports"), author: Optional(""), title: "Clemson vs. Florida State game postponed hours before kickoff as teams disagreed about whether to play - CBS Sports", articleDescription: Optional("A Clemson player's late positive test for COVID-19 is the reason for the abrupt postponement"), url: https://www.cbssports.com/college-football/news/clemson-vs-florida-state-game-postponed-hours-before-kickoff-as-teams-disagreed-about-whether-to-play/, urlToImage: https://sportshub.cbsistatic.com/i/r/2019/01/31/3cfd9702-8ef4-4f0d-9cc6-2601f4b3ad9c/thumbnail/1200x675/d00f9bb392291c844de43984526b773e/clemson.jpg, publishedAt: 2020-11-21 15:28:00 +0000, content: Optional("No. 4 Clemson and Florida State were set to kick off at noon ET in Tallahassee, Florida, a game that would have marked Tigers quarterback Trevor Lawrence's return to action following a positive COVID… [+3139 chars]")) ......
articles - description
"Expected String value but found null instead."
Change
let description: String
to
let description: String?
It says there is no value at the first value, can you post the json response what you're getting as that would be useful. Another possibility could be that the json response is in a different order than what you are decoding.
You could just change description to an optional as that would get rid of the problem but then you wouldn't have a description so it doesn't fully solve the problem
let description: String?

iOS Parsing text file Swift

I try to parse my txt file, i have question and 5 answers in txt file, and i want to save question to separate variable, and similar answers.
My code:
do {
let path = Bundle.main.path(forResource: "data", ofType: "txt")
let source = try? String.init(contentsOfFile: path!)
var elements = source?.components(separatedBy: "\n")
var parsedObject = [[String: String]]()
for i in 0..<(elements?.count)! - 1 {
let objects = [String : String]()
let element = elements![i]
//print(element)
let objectsElement = element.components(separatedBy: "\r")
let question = objectsElement[0]
let answer1 = objectsElement[1]
let answer2 = objectsElement[2]
let answer3 = objectsElement[3]
let anserr4 = objectsElement[4]
let answer5 = objectsElement[5]
print(question, answer1, answer2, answer3, anserr4, answer5)
print(objectsElement)
}
}
Error which i received:
Thread 1: Fatal error: Index out of range
If i comment this code:
let question = objectsElement[0]
let answer1 = objectsElement[1]
let answer2 = objectsElement[2]
let answer3 = objectsElement[3]
let anserr4 = objectsElement[4]
let answer5 = objectsElement[5]
print(question, answer1, answer2, answer3, anserr4, answer5)
I will get such output:
Structure file:
https://drive.google.com/file/d/1ah1Mk_WY3b_qbqKM18nxxPT1rNXFiISa/view?usp=sharing
Parsing a text file is very, very tedious.
Here is a quick&dirty converter from your .txt file to JSON using regular expression.
The JSON dictionaries have the format
question (String), the question text
answers([String]), an array of answers
correctAnswerIndex (Int), the zero-based index of the correct answer in answers
number (Int), the question number.
The regex pattern searches for
one or more digits or one letter in range A...E (is captured) - (\\d+|[A-E])
followed by a dot and a whitespace character - \\.\\s
followed by one or more arbitrary characters (is captured) - (.+)
let url = Bundle.main.url(forResource: "data", withExtension: "txt")!
let string = try! String(contentsOf: url, encoding: .utf8)
let pattern = "(\\d+|[A-E])\\.\\s(.+)"
let regex = try! NSRegularExpression(pattern: pattern)
var result = [[String:Any]]()
var question = [String:Any]()
var answers = [String]()
var answerCounter = 0
let matches = regex.matches(in: string, range: NSRange(string.startIndex..<string.endIndex, in: string))
for match in matches {
let index = string[Range(match.range(at: 1), in: string)!]
var text = string[Range(match.range(at: 2), in: string)!]
if let ordinal = Int(index) {
question["number"] = ordinal
question["question"] = text
} else {
if text.hasPrefix("*") {
text = text.dropFirst()
question["correctAnswerIndex"] = answerCounter
}
answers.append(String(text))
answerCounter += 1
if answerCounter == 5 {
question["answers"] = answers
result.append(question)
question = [:]
answers = []
answerCounter = 0
}
}
}
let jsonData = try! JSONSerialization.data(withJSONObject: result)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
Save that JSON in the bundle as data.json and delete the text file
Now you can decode the JSON into a struct with
struct Question : Decodable {
let question : String
let answers : [String]
let correctAnswerIndex : Int
let number : Int
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")!
let data = try! Data(contentsOf: url)
do {
let result = try JSONDecoder().decode([Question].self, from: data)
print(result)
} catch {
print(error)
}
Have a look a your txt file by printing source. You'll get something like this:
Optional("1. A patient with ischemic heart disease has been administered inosine which is an intermediate metabolite in the synthesis of:\n A. Metalloproteins\n B. Glycoproteins\n C. Ketone bodies\n D. *Purine nucleotides\n E. Lipoproteins\n\n2. Rates of chemical reactions of the same order are compared by:\n A. *Constant of chemical reaction rate\n B. Endpoint of a reaction\n C. Change in the concentration of the reaction products\n D. Change in the reactants concentration\n E. Chemical reaction rate\n\n ...etc)
As you can see, Questions are separated by \n\n
var elements = source?.components(separatedBy: "\n\n")
Then you can separate the question and its answers by \n
let objectsElement = element.components(separatedBy: "\n")
Make sure in your txt file that Questions are separated by two newlines, and that question have only one new line between them.
In your case one way to achieve what you want is this:
do {
let path = Bundle.main.path(forResource: "Data", ofType: "txt")
let source = try? String.init(contentsOfFile: path!)
var elements = source?.components(separatedBy: "\n\n") // Here is the change
var parsedObject = [[String: String]]()
for i in 0..<(elements?.count)! - 1 {
let objects = [String : String]()
let element = elements![i]
let objectsElement = element.components(separatedBy: "\n") // And Here
if objectsElement.count > 1 {
let question = objectsElement[0]
let answer1 = objectsElement[1]
let answer2 = objectsElement[2]
let answer3 = objectsElement[3]
let answer4 = objectsElement[4]
let answer5 = objectsElement[5]
print("Question: \(question)")
print("Answer: \(answer1)")
print("Answer: \(answer2)")
print("Answer: \(answer3)")
print("Answer: \(answer4)")
print("Answer: \(answer5)")
}
}
}
If you are not changing your text file format then in this way you can get the solution. For now you can try this solution but search for better one or if I will get one I will post the answer soon.
You are trying to access elements in the location 0 to 5 of the array objectsElement. So whenever the objectsElement.count < 6 its going to throw Thread 1: Fatal error: Index out of range
Seeing your output i think every time objectsElement has only one element in it.

Update Firebase data in tableView cell on tap

I am using Firebase real time database to display posts in a tableView. I want to increase the number of likes of a specific post when the user double taps the corresponding cell.
I got the double tap working and am already printing out the correct indexPath.
override func viewDidLoad() {
super.viewDidLoad()
// double tap
let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(sender:)))
doubleTapGestureRecognizer.numberOfTapsRequired = 2
postTableView.addGestureRecognizer(doubleTapGestureRecognizer)
}
And here's what I tried according to the Firebase documentation to update the likes:
func handleDoubleTap(sender: UITapGestureRecognizer) {
let touchPoint = sender.location(in: postTableView)
if let indexPath = postTableView.indexPathForRow(at: touchPoint) {
print(indexPath)
let post = posts[indexPath.row]
let oldLikes = post.likes
let newLikes = oldLikes! + 1
let postUpdates = ["\(post.likes)": newLikes]
database.updateChildValues(postUpdates)
postTableView.reloadData()
}
}
It doesn't throw any errors but is not working.
This is the database structure:
And here's how I declared the database:
struct post {
let author : String!
let creationDateTime : String!
let content : String!
let likes : Int!
}
And in viewDidLoad
let database = FIRDatabase.database().reference()
This is how I create a post:
#IBAction func savePost(_ segue:UIStoryboardSegue) {
let addPostVC = segue.source as! AddPostViewController
let author = currentUser.displayName
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let dateResult = formatter.string(from: date)
let creationDateTime = "\(dateResult)"
let content = addPostVC.passTextContent
let likes = 0
let post : [String : AnyObject] = ["author" : author as AnyObject,
"content" : content as AnyObject,
"creationDateTime" : creationDateTime as AnyObject,
"likes" : likes as AnyObject]
database.child("Posts").childByAutoId().setValue(post)
}
And this how I retrieve the data in viewDidLoad
database.child("Posts").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let postID = (snapshot.value as? NSDictionary)?["postID"] as? String ?? ""
let author = (snapshot.value as? NSDictionary)?["author"] as? String ?? ""
let content = (snapshot.value as? NSDictionary)?["content"] as? String ?? ""
let creationDateTime = (snapshot.value as? NSDictionary)?["creationDateTime"] as? String ?? ""
let likes = (snapshot.value as? NSDictionary)?["likes"] as? Int ?? 0
self.posts.insert(post(postID: postID, author: author, creationDateTime: creationDateTime, content: content, likes: likes), at: 0)
self.postTableView.reloadData()
})
You have a problem with your database reference. When you do let database = FIRDatabase.database().reference() then you are refering to the main node in your database structure. This means that you will be working with the structure under the root in your database Json. The only child of it will be the Posts key.
When you do
let postUpdates = ["\(post.likes)": newLikes]
database.updateChildValues(postUpdates)
you are trying to update the value under the root node, which clearly does not exist. The only reference it can find is the key Posts.
In order to perform the update in the correct place, you could get child references from your main reference, especially one to the post you are interested in updating.
For example, you could do the following:
let postReference = database.child("Here goes the post Id").
Then, you will be able to use updateChildValues correctly on this new reference, since it will be updating the specific post.
Another thing that may be used wrong is the dictionary that is being sent to the updateChildValues. The structure of the dictionary that you have to provide is the following:
["key that you want to update": new value]
So in your case, instead of providing the previous like count and the new like count, you should provide a dictionary as the following:
let postUpdates = ["likes": newLikes]

How do I create an array of objects based on array of JSON objects using SwiftyJSON (in Swift)?

Here's the JSON I am getting from the server:
{
"items": [
{
"name": "Shampoo",
"price": 9
},
...
]
}
Here's my Item class in Swift:
class Item {
var name: String
var price: Float
init(name: String, price: Float) {
self.name = name
self.price = price
}
}
I want to create an Item object for each JSON object in the items array using SwiftyJSON. So I thought I'd just loop through the Swift array that SwiftyJSON will create for me and voila. But SwiftyJSON throws an error saying items is not an array. I tried subscripting it as a dictionary but you can't (I thought you could) iterate through a dictionary in a for-loop.
Here's the code I have tried:
let json = JSON(data: data) // data is the JSON from the server (above) and isn't nil
let items = json["items"].array // this is nil and where SwiftyJSON throws the error.
// error checking and optional unwrapping etc.
for item in items {
var itemsList: [Item] = []
itemsList.append(Item(name: item["name"], price: item["price"]))
}
I feel like this should be pretty easy so if anyone can find where I went wrong I'd really appreciate it. Thanks!
Check out ObjectMapper, it is another JSON parser library for swift.
It support mapping an array out of the box.
Just declare your server response object like:
class ServerResponse: Mappable {
var array: [Item]?
required init?(_ map: Map) {
}
// Mappable
func mapping(map: Map) {
array <- map["items"]
}
}
This is how i do in my project...
guard let cityName = json["city"]["name"].string else {return}
guard let cityID = json["city"]["id"].int else {return}
var allForecasts = [Forecast]()
guard let allStuff = json["list"].array else {return}
for f in allStuff {
guard let date = f["dt"].double else {continue}
let dateUnix = NSDate(timeIntervalSince1970: date)
guard let temp = f["main"]["temp"].double else {continue}
guard let tempMin = f["main"]["temp_min"].double else {continue}
guard let tempMax = f["main"]["temp_max"].double else {continue}
guard let pressure = f["main"]["pressure"].double else {continue}
guard let humidity = f["main"]["humidity"].double else {continue}
guard let description = f["weather"][0]["description"].string else {continue}
guard let icon = f["weather"][0]["icon"].string else {continue}
guard let wind = f["wind"]["speed"].double else {continue}
let weather = Forecast(temperature: temp, maximum: tempMax, minimum: tempMin, description: description, icon: icon, humidity: humidity, pressure: pressure, wind: wind, date: dateUnix)
allForecasts.append(weather)
}
let fullWeather = City(cityID: cityID, cityName: cityName, forecasts: allForecasts)
I think it's helpful.

Resources