I'm a new at swift, please help.
Try to archive and unarchive object and write/read it on disk.
I have a class FourLines
class FourLines: NSObject, NSCoding, NSSecureCoding, NSCopying {
static var supportsSecureCoding: Bool{
return true
}
private static let linesKey = "linesKey"
var lines: [String]?
override init() {
}
required init?(coder aDecoder: NSCoder) {
print("FourLines init")
lines = aDecoder.decodeObject(forKey: FourLines.linesKey) as? [String]
}
func encode(with aCoder: NSCoder) {
print("FourLines encode")
if let saveLines = lines {
aCoder.encode(saveLines, forKey: FourLines.linesKey)
}
}
func copy(with zone: NSZone? = nil) -> Any {
print("copy with zone")
let copy = FourLines()
if let linesToCopy = lines {
var newLines = Array<String>()
for line in linesToCopy {
newLines.append(line)
}
copy.lines = newLines
}
return copy
}
}
In ViewController i try to save and read data:
class ViewController: UIViewController {
private static let linesKey = "linesKey"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let fileURL = self.dataFileUrl()
if FileManager.default.fileExists(atPath: fileURL.path!) {
let codedData = try! Data(contentsOf: fileURL as URL)
print(codedData)
let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: codedData)
if unarchiver.containsValue(forKey: ViewController.linesKey) {
print("viewDidLoad contains value")
} else {
print("viewDidLoad doesn't conains value")
}
let fourLines = unarchiver.decodeObject(forKey: ViewController.linesKey) as! FourLines?
print(fourLines?.lines?.count)
}
let app = UIApplication.shared
NotificationCenter.default.addObserver(self, selector: #selector(self.applicationWillResignActive(notification:)), name: UIApplication.willResignActiveNotification, object: app)
}
#objc func applicationWillResignActive(notification: NSNotification) {
print("applicationWillResignActive")
let fileURL = self.dataFileUrl()
print(fileURL)
let fourLines = FourLines()
let array = (self.lineFields as NSArray).value(forKey: "text") as! [String]
fourLines.lines = array
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(fourLines, forKey: ViewController.linesKey)
let data = archiver.encodedData
do {
try data.write(to: fileURL as URL)
} catch {
print("Error is \(error)")
}
}
#IBOutlet var lineFields: [UITextField]!
func dataFileUrl() -> NSURL {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
var url: NSURL?
url = URL(fileURLWithPath: "") as NSURL
do {
try url = urls.first!.appendingPathComponent("data.archive") as NSURL
} catch {
print("Error is \(error)")
}
return url!
}
}
When I resign app encode method invokes:
FourLines encode
And when I try to load it, I see file is created and it contains value, but I always have nil while decode fourLines object:
322 bytes
viewDidLoad contains value
nil
And init? coder aDecoder and copy with zone never invoke.
What do I wrong?
Your issue is that you never initialized your lines array. Change its declaration to non optional and initialize it with an empty array. Try like this:
class FourLines: NSObject, NSCoding, NSSecureCoding, NSCopying {
static var supportsSecureCoding: Bool { return true }
private static let linesKey = "linesKey"
var lines: [String] = []
override init() { }
required init?(coder aDecoder: NSCoder) {
print(#function)
lines = aDecoder.decodeObject(forKey: FourLines.linesKey) as? [String] ?? []
}
func encode(with aCoder: NSCoder) {
print(#function)
aCoder.encode(lines, forKey: FourLines.linesKey)
}
func copy(with zone: NSZone? = nil) -> Any {
print(#function)
let copy = FourLines()
var newLines = Array<String>()
for line in lines {
newLines.append(line)
}
copy.lines = newLines
return copy
}
}
Playground testing:
let fourLines = FourLines()
fourLines.lines = ["line1","line2","line3","line4"]
let data = try! NSKeyedArchiver.archivedData(withRootObject: fourLines, requiringSecureCoding: true)
let decodedFourlInes = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! FourLines
decodedFourlInes.lines // ["line1", "line2", "line3", "line4"]
Btw If you are trying to persist your textfield values your ViewController should look something like this:
class ViewController: UIViewController {
#IBOutlet var lineFields: [UITextField]!
private static let dataFileUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("lines.plist")
override func viewDidLoad() {
super.viewDidLoad()
do {
let lines = (try NSKeyedUnarchiver
.unarchiveTopLevelObjectWithData(Data(contentsOf: ViewController.dataFileUrl)) as! FourLines)
.lines
var index = 0
lineFields.forEach {
$0.addTarget(self, action: #selector(editingDidEnd), for: .editingDidEnd)
$0.text = lines[index]
index += 1
}
} catch {
print(error)
}
}
#objc func editingDidEnd(_ textField: UITextField) {
print(#function)
let fourLines = FourLines()
fourLines.lines = lineFields.map { $0.text! }
do {
try NSKeyedArchiver
.archivedData(withRootObject: fourLines, requiringSecureCoding: true)
.write(to: ViewController.dataFileUrl)
} catch {
print(error)
}
}
}
Related
I have two model swift files under below.
// Item.swift
import UIKit
class Item: NSObject, NSCoding {
var name: String
var valueInDollars: Int
var serialNumber: String?
let dateCreated: Date
let itemKey: String
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(dateCreated, forKey: "dateCreated")
aCoder.encode(itemKey, forKey: "itemKey")
aCoder.encode(serialNumber, forKey: "serialNumber")
aCoder.encode(valueInDollars, forKey: "valueInDollars")
}
required init(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as! String
dateCreated = aDecoder.decodeObject(forKey: "dateCreated") as! Date
itemKey = aDecoder.decodeObject(forKey: "itemKey") as! String
serialNumber = aDecoder.decodeObject(forKey: "serialNumber") as! String?
valueInDollars = aDecoder.decodeInteger(forKey: "valueInDollars")
super.init()
}
}
// ItemStore.swift
import UIKit
class ItemStore {
var allItems = [Item]()
let itemArchiveURL: URL = {
let documentsDirectories =
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = documentsDirectories.first!
return documentDirectory.appendingPathComponent("items.archive")
}()
func saveChanges() -> Bool {
print("Saving items to: \(itemArchiveURL.path)")
return NSKeyedArchiver.archiveRootObject(allItems, toFile: itemArchiveURL.path)
}
}
These two model files confirming to NSCoding protocol and using archiveRootObject to archive the data.
But the archiveRootObject is deprecated, and the NSCoding is not as safe as the NSSecureCoding, how can I tweak the code to adjust all of these?
You can rewrite you saveChanges function to something like this:
func saveChanges() -> Bool {
print("Saving items to: \(itemArchiveURL.path)")
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: allItems, requiringSecureCoding: false)
try data.write(to: itemArchiveURL)
}
catch {
print("Error archiving data: \(error)")
return false
}
return true
}
I have an array of the custom object TemplateIndex, which I am trying to save and unsave to NSUserDefaults. But when I decode it, I get the following error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Here is my custom object:
class TemplateIndex: NSObject, NSCoding {
var identifier: String
var sectionNumber: Int
var indexNumber: Int
init(identifier: String, sectionNumber: Int, indexNumber: Int) {
self.identifier = identifier
self.sectionNumber = sectionNumber
self.indexNumber = indexNumber
}
required init?(coder aDecoder: NSCoder) {
self.identifier = aDecoder.decodeObject(forKey: "identifier") as! String
self.sectionNumber = aDecoder.decodeObject(forKey: "sectionNumber") as! Int
self.indexNumber = aDecoder.decodeObject(forKey: "indexNumber") as! Int
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.identifier, forKey: "identifier")
aCoder.encode(self.sectionNumber, forKey: "sectionNumber")
aCoder.encode(self.indexNumber, forKey: "indexNumber")
}
}
var favouriteTemplateIdentifiersArray: [TemplateIndex] = []
And here are my save and unsave functions:
func unarchiveFaveTemplates() {
guard let unarchivedObject = UserDefaults.standard.data(forKey: "faveTemplates") else {
return
}
guard let unarchivedFaveTemplates = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(unarchivedObject) else {
return
}
favouriteTemplateIdentifiersArray = unarchivedFaveTemplates as! [TemplateIndex]
print("array opened")
}
func saveFaveTemplates() {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: favouriteTemplateIdentifiersArray, requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: "faveTemplates")
UserDefaults.standard.synchronize()
print("array saved")
} catch {
fatalError("can't encode data.")
}
}
Any help is appreciated, thankyou!
EDIT: Working Code
class TemplateIndex: Codable {
var identifier: String
var sectionNumber: Int
var indexNumber: Int
init(identifier: String, sectionNumber: Int, indexNumber: Int) {
self.identifier = identifier
self.sectionNumber = sectionNumber
self.indexNumber = indexNumber
}
}
func unarchiveFaveTemplates() {
if let data = UserDefaults.standard.value(forKey: "faveTemplates") as? Data,
let newArray = try? JSONDecoder().decode(Array<TemplateIndex>.self, from: data) {
print("opened")
favouriteTemplateIdentifiersArray = newArray
}
}
func saveFaveTemplates() {
if let data = try? JSONEncoder().encode(favouriteTemplateIdentifiersArray) {
UserDefaults.standard.set(data, forKey: "faveTemplates")
}
print("changes saved")
}
Forget about NSCoding and NSKeyedArchiver , you need to use Codable
struct TemplateIndex:Codable {
var identifier: String
var sectionNumber,indexNumber: Int
}
guard let data = UserDefaults.standard.data(forKey: "faveTemplates") else {
return
}
do {
let arr = try JSONDecoder().decode([TemplateIndex].self,from:data)
let data = try JSONEncoder().encode(arr)
UserDefaults.standard.set(data, forKey: "faveTemplates")
} catch {
print(error)
}
The application uses iCloud to store the objects conformed to Codable protocol using NSKeyedArchiver/NSKeyedUnarchiver.
Synchronization between devices is OK, except when the application is reinstalled on the device (or installed on a new device) and the file with data exists – in this case
NSKeyedUnarchiver.unarchiveObject(withFile: filePathe) return nil.
How to get data from an existing file from the iCloud when I install the application on a new device (reinstall on the same device)?
class ViewController: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var weightLabel: UILabel!
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var weightTextField: UITextField!
var iCloudContainer: URL? {
return FileManager().url(forUbiquityContainerIdentifier: nil)
}
func getFilePath(container: URL, fileName: String) -> String {
let filePath = container.appendingPathComponent(fileName).path
return filePath
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func fetchButtonPressed(_ sender: UIButton) {
let container = self.iCloudContainer
let filePathe = getFilePath(container: container!, fileName: "Person")
if let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: filePathe) as? Data {
if let person = try? JSONDecoder().decode(Person.self, from: jsonData) {
nameLabel.text = person.name
weightLabel.text = String(person.weight)
} else {
nameLabel.text = "No data loaded"
weightLabel.text = "No data loaded"
}
} else {
nameLabel.text = "No data loaded"
weightLabel.text = "No data loaded"
}
}
#IBAction func saveButtonPressed(_ sender: UIButton) {
let container = self.iCloudContainer
let filePathe = getFilePath(container: container!, fileName: "Person")
let person = Person(name: nameTextField.text!, weight: Double(weightTextField.text!)!)
let jsonData = try? JSONEncoder().encode(person)
NSKeyedArchiver.archiveRootObject(jsonData!, toFile: filePathe)
}
To get data from iCloud container to your local UbiquityContainer you should use NSMetadata to find and download items from iCloud to device.
lazy var metadataQuery : NSMetadataQuery = {
let query = NSMetadataQuery()
query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
query.predicate = NSPredicate(format: "%K CONTAINS %#", NSMetadataItemFSNameKey, "List")
NotificationCenter.default.addObserver(self, selector: #selector(didFinishGathering), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: query)
NotificationCenter.default.addObserver(self, selector: #selector(didFinishGathering), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query)
return query
}()
override func viewDidLoad() {
super.viewDidLoad()
self.metadataQuery.start()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func didFinishGathering(notification: Notification?) {
let query = notification?.object as? NSMetadataQuery
query?.enumerateResults { (item: Any, index: Int, stop: UnsafeMutablePointer<ObjCBool>) in
let metadataItem = item as! NSMetadataItem
if isMetadataItemDownloaded(item: metadataItem) == false {
let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as! URL
try? FileManager.default.startDownloadingUbiquitousItem(at: url)
}
}
guard let queryresultsCount = query?.resultCount else { return }
for index in 0..<queryresultsCount {
let item = query?.result(at: index) as? NSMetadataItem
let itemName = item?.value(forAttribute: NSMetadataItemFSNameKey) as! String
let container = filesCoordinator.iCloudContainer
let filePath = filesCoordinator.getFilePath(container: container!, fileName: "TaskList")
let addressPath = filesCoordinator.getFilePath(container: container!, fileName: "CategoryList")
if itemName == "TaskList" {
if let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? Data {
if let person = try? JSONDecoder().decode(Person.self, from: jsonData) {
nameLabel.text = person.name
weightLabel.text = String(person.weight)
} else {
nameLabel.text = "NOT decoded"
weightLabel.text = "NOT decoded"
}
} else {
nameLabel.text = "NOT unarchived"
weightLabel.text = "NOT unarchived"
}
} else if itemName == "CategoryList" {
if let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: addressPath) as? Data {
if let address = try? JSONDecoder().decode(Address.self, from: jsonData) {
streetLabel.text = address.street
houseLabel.text = String(address.house)
} else {
streetLabel.text = "NOT decoded"
houseLabel.text = "NOT decoded"
}
} else {
streetLabel.text = "NOT unarchived"
houseLabel.text = "NOT unarchived"
}
}
}
}
func isMetadataItemDownloaded(item : NSMetadataItem) -> Bool {
if item.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String == NSMetadataUbiquitousItemDownloadingStatusCurrent {
return true
} else {
return false
}
}
#IBAction func saveButtonPressed(_ sender: UIButton) {
let container = filesCoordinator.iCloudContainer
let personPath = filesCoordinator.getFilePath(container: container!, fileName: "TaskList")
let addressPath = filesCoordinator.getFilePath(container: container!, fileName: "CategoryList")
let person = Person(name: nameTextField.text!, weight: Double(weightTextField.text!)!)
let jsonPersonData = try? JSONEncoder().encode(person)
NSKeyedArchiver.archiveRootObject(jsonPersonData!, toFile: personPath)
let address = Address(street: streetTextField.text!, house: Int(houseTextField.text!)!)
let jsonAddressData = try? JSONEncoder().encode(address)
NSKeyedArchiver.archiveRootObject(jsonAddressData!, toFile: addressPath)
}
}// end of the class
I am attempting to convert an old swift app to 2.0 and can't seem to get past this bit of code in this function:
func documentsPathForFileName(name: String) -> String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true);
let path = paths[0] ;
let fullPath = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(name)
return fullPath
}
On the "let fullPath" line I get the error "Cannot convert return expression of type 'NSURL' to return type 'String'"
Here is the full .swift file:
import UIKit
class CardViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIScrollViewDelegate {
#IBOutlet weak var SecondCaptureButton: UIBarButtonItem!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var OpenButton: UIBarButtonItem!
#IBOutlet weak var MainCaptureButton: UIButton!
var imagePicker: UIImagePickerController!
/* func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
} */
override func shouldAutorotate() -> Bool {
return false
}
override func viewDidLoad() {
super.viewDidLoad()
OpenButton.target = self.revealViewController()
OpenButton.action = Selector("revealToggle:")
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
MainCaptureButton.layer.borderColor = UIColor.blackColor().CGColor
MainCaptureButton.layer.cornerRadius = 10
MainCaptureButton.layer.borderWidth = 1
MainCaptureButton.titleLabel?.textAlignment = NSTextAlignment.Center
let possibleOldImagePath = NSUserDefaults.standardUserDefaults().objectForKey("path") as! String?
if let oldImagePath = possibleOldImagePath {
let oldFullPath = self.documentsPathForFileName(oldImagePath)
let oldImageData = NSData(contentsOfFile: oldFullPath)
// here is your saved image:
if let oldImage = UIImage(data: oldImageData!) {
imageView.image = oldImage
print("Old Photo Retrieved")
self.view.bringSubviewToFront(imageView)
}
}
}
func documentsPathForFileName(name: String) -> String {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true);
let path = paths[0] ;
let fullPath = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(name)
return fullPath
}
#IBAction func TakePhoto(sender: AnyObject) {
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .Camera
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
imagePicker.dismissViewControllerAnimated(true, completion: nil)
self.view.sendSubviewToBack(MainCaptureButton)
imageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
let imageData = UIImageJPEGRepresentation(imageView.image!, 1)
let relativePath = "image_\(NSDate.timeIntervalSinceReferenceDate()).jpg"
let path = self.documentsPathForFileName(relativePath)
imageData!.writeToFile(path, atomically: true)
NSUserDefaults.standardUserDefaults().setObject(relativePath, forKey: "path")
NSUserDefaults.standardUserDefaults().synchronize()
print("New Photo Saved")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Your method is defined to return a String:
func documentsPathForFileName(name: String) -> String { ...
but you are trying to return an NSURL. You could either change your method signature to return an NSURL:
func documentsPathForFileName(name: String) -> NSURL { ...
or you could return a string representation of the URL:
return fullPath.absoluteString
The error message is very clear.
fullPath is an NSURL instance, the return value is String
Either change the return value
func documentsPathForFileName(name: String) -> NSURL {
let fullPath = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(name)
return fullPath
}
Or return a string
func documentsPathForFileName(name: String) -> String {
let fullPath = NSTemporaryDirectory().stringByAppendingPathComponent(name)
return fullPath
}
I omitted the local variable path because it's not used in the snippet.
How do I used a string value from a function in a another class to update an UILabel on my ViewController?
Here is my code:
View controller:
import UIKit
class ViewController: UIViewController, dataEnterdDelegate {
#IBOutlet weak var auaTempLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let weather2 = WeatherService2()
weather2.getWeatherData("Oranjestad,AW")
}
**func userDidEnterInformation(info: NSString)
{
testLabel!.text = info as String
}**
func setLabel2(information: String)
{
auaTempLabel.text = information
}
The other class named WeatherService2 contain the following codes:
**protocol dataEnterdDelegate{
func userDidEnterInformation(info:NSString)
}**
Class WeatherService2{
var currentTempeture:String?
let targetVC = ViewController()
**var delegate: dataEnterdDelegate?**
func getWeatherData(urlString:String)
{
let url = NSURL(string: urlString)!
let sqlQuery = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=\"\(url)\")"
let endpoint = "https://query.yahooapis.com/v1/public/yql?q=\(sqlQuery)&format=json"
let testString = (String(endpoint))
getData(testString)
}
func getData(request_data: String)
{
let requestString:NSString = request_data.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let url_with_data = NSURL(string: requestString as String)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url_with_data){
(data, response, error) in dispatch_async(dispatch_get_main_queue(), {
if data == nil
{
print("Failed loading HTTP link")
}else{
self.setLabel(data!)
}
})
}
task.resume()
}
func setLabel(weatherData:NSData)
{
enum JSONErrors: ErrorType
{
case UserError
case jsonError
}
do{
let jsonResults = try NSJSONSerialization.JSONObjectWithData(weatherData, options: .AllowFragments)
if let city = jsonResults["query"] as? NSDictionary
{
if let name = city["results"] as? NSDictionary
{
if let channel = name["channel"] as? NSDictionary
{
if let item = channel["item"] as? NSDictionary
{
if let condition = item["condition"] as? NSDictionary
{
if let temp = condition["temp"] as? String
{
setTemp(temp)
**delegate!.userDidEnterInformation(temp)**
}
}
}
}
}
}
}
catch {
print("Failed to load JSON Object")
}
}
func setTemp(tempeture:String)
{
self.currentTempeture = tempeture
}
func getTemp() ->String
{
return self.currentTempeture!
}
}
The code runs fine and everything but I get an error "Fatal error: unexpectedly found nil while unwrapping an Optional value" when I try to update the UILabel in my ViewController.
When I used the print("The return value is: "+information) in the view controller class it print the return value correctly.
This is the reason I'm confused right now because I don't know why I still getting the "Fatal error: unexpectedly found nil while unwrapping an Optional value" when trying to use this value to update my UILabel.
Can anyone help me with this problem?
Thanks in advance
For that you have to create delegate method.
In viewController you create delegate method and call it from where you get response and set viewController.delegate = self
I could not explain more you have to search for that and it will works 100% .
All the best.
I manage to fix this issue by doing the following:
I create the following class
- Item
- Condition
- Channel
These classes implement the JSONPopulator protocol.
The JSONPopulator protocol:
protocol JSONPopulator
{
func populate(data:AnyObject)
}
Item class:
class Item: JSONPopulator
{
var condition:Condition?
func getCondition() ->Condition
{
return condition!
}
func populate(data: AnyObject)
{
condition = Condition()
condition?.populate(data)
}
}
Condition class:
class Condition:JSONPopulator
{
var arubaTemp:String?
var channel:NSDictionary!
func getArubaTemp()->String
{
return arubaTemp!
}
func getBonaireTemp() ->String
{
return bonaireTemp!
}
func getCuracaoTemp()->String
{
return curacaoTemp!
}
func populate(data: AnyObject)
{
if let query = data["query"] as? NSDictionary
{
if let results = query["results"] as? NSDictionary
{
if let channel = results["channel"] as? NSDictionary
{
self.channel = channel
if let location = channel["location"] as? NSDictionary
{
if let city = location["city"] as? String
{
if city.containsString("Oranjestad")
{
switch city
{
case "Oranjestad":
arubaTemp = getTemp()
print(arubaTemp)
default:
break
}
}
}
}
}
}
}
}
func getTemp() ->String
{
var temp:String?
if let item = self.channel["item"] as? NSDictionary
{
if let condition = item["condition"] as? NSDictionary
{
if let tempeture = condition["temp"] as? String
{
print(tempeture)
temp = tempeture
}
}
}
print(temp)
return temp!
}
}
Channel class:
class Channel: JSONPopulator
{
var item:Item?
var unit:Unit?
var request_city:String?
func setRequestCity(request_city:String)
{
self.request_city = request_city
}
func getRequestCity() ->String
{
return request_city!
}
func getItem() -> Item
{
return item!
}
func getUnit() -> Unit
{
return unit!
}
func populate(data: AnyObject)
{
item = Item()
item?.populate(data)
}
}
The WeatherService class that handles the function of parsing the JSON object. This class implement a WeatherServiceCallBack protocol.
The WeatherServiceCallBack protocol:
protocol WeatherServiceCallBack
{
func arubaWeatherServiceService( channel:Channel)
func arubaWeatherServiceFailure()
}
WeatherService class:
class WeatherService
{
var weatherServiceCallBack:WeatherServiceCallBack
var requestCity:String?
init(weatherServiceCallBack: WeatherServiceCallBack)
{
self.weatherServiceCallBack = weatherServiceCallBack
}
internal func checkCity(city:String)
{
switch (city)
{
case "Oranjestad,AW":
requestCity = city
getWeatherData(requestCity!)
default:
break
}
}
func getWeatherData(urlString:String)
{
let url = NSURL(string: urlString)!
let sqlQuery = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=\"\(url)\")"
let endpoint = "https://query.yahooapis.com/v1/public/yql?q=\(sqlQuery)&format=json"
let testString = (String(endpoint)
executeTask(testString)
}
func executeTask(request_data: String)
{
let requestString:NSString = request_data.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let url_with_data = NSURL(string: requestString as String)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url_with_data){
(data, response, error) in dispatch_async(dispatch_get_main_queue(), {
if data == nil
{
print("Failed loading HTTP link")
}else{
self.onPost(data!)
}
})
}
task.resume()
}
func onPost(data:NSData)
{
enum JSONErrors: ErrorType
{
case UserError
case jsonError
}
do{
let jsonResults = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
print(jsonResults)
if let city = jsonResults["query"] as? NSDictionary
{
if let name = city["count"] as? Int
{
if name == 0
{
weatherServiceCallBack.arubaWeatherServiceFailure()
}
}
}
if let requestCity_check = jsonResults["query"] as? NSDictionary
{
if let results = requestCity_check["results"] as? NSDictionary
{
if let channel = results["channel"] as? NSDictionary
{
if let location = channel["location"] as? NSDictionary
{
if let city = location["city"] as? String
{
requestCity = city
let channel = Channel()
channel.setRequestCity(requestCity!)
channel.populate(jsonResults)
weatherServiceCallBack.arubaWeatherServiceService(channel)
}
}
}
}
}
}catch {
print("Failed to load JSON Object")
}
}
}
In the ViewController class (I add some animation to the UILabel so it can flip from Fahrenheit to Celsius):
class ViewController: UIViewController, WeatherServiceCallBack
{
var weather:WeatherService?
var aua_Tempeture_in_F:String?
var aua_Tempeture_in_C:String?
var timer = NSTimer()
#IBOutlet var aua_Temp_Label: UILabel!
let animationDuration: NSTimeInterval = 0.35
let switchingInterval: NSTimeInterval = 5 //10
override func viewDidLoad() {
super.viewDidLoad()
weather = WeatherService(weatherServiceCallBack: self)
weather?.checkCity("Oranjestad,AW")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func animateTemptext()
{
self.timer = NSTimer.scheduledTimerWithTimeInterval(7.0, target: self, selector: Selector("tempConvertionTextSwitch"), userInfo: nil, repeats: true)
}
func setTempinCelsius(temp_string:String)
{
aua_Tempeture_in_F = "\(temp_string)°F"
let convertedString = convertFahrenheittoCelsius(temp_string)
aua_Tempeture_in_C = "\(convertedString)°C"
aua_Temp_Label.text = aua_Tempeture_in_C
animateTemptext()
}
func convertFahrenheittoCelsius(currentTemp:String) ->String
{
let tempTocelsius = (String(((Int(currentTemp)! - 32) * 5)/9))
return tempTocelsius
}
#objc func tempConvertionTextSwitch()
{
CATransaction.begin()
CATransaction.setAnimationDuration(animationDuration)
CATransaction.setCompletionBlock{
let delay = dispatch_time(DISPATCH_TIME_NOW,Int64(self.switchingInterval * NSTimeInterval(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue())
{
}
}
let transition = CATransition()
transition.type = kCATransitionFade
if aua_Temp_Label.text == aua_Tempeture_in_F
{
aua_Temp_Label.text = aua_Tempeture_in_C
}else if aua_Temp_Label.text == aua_Tempeture_in_C
{
aua_Temp_Label.text = aua_Tempeture_in_F
}else if aua_Temp_Label == ""
{
aua_Temp_Label.text = aua_Tempeture_in_C
}
aua_Temp_Label.layer.addAnimation(transition, forKey: kCATransition)
CATransaction.commit()
}
func arubaWeatherServiceFailure() {
}
func arubaWeatherServiceService(channel: Channel)
{
let requested_city = channel.getRequestCity()
let items = channel.getItem()
let aua_Temp = items.getCondition().getArubaTemp()
setTempinCelsius(aua_Temp)
}
}
Reference:
iOS 8 Swift Programming Cookbook Solutions Examples for iOS Apps book
iOS 8 Programming Fundamentals with Swift Swift, Xcode, and Cocoa Basics book
Hope it help the once that had the same problem