I'm new to swift programming.
I wrote a code which gets xml output from rss feed and parse it into NSDATA type,
then I want to get title and image src values from it in a loop and send them to a table view to create a list.
when I get those values manually I mean like :
let appName = xml["rss"]["channel"]["item"][0]["title"].element!.text!
let appUrl = xml["rss"]["channel"]["item"][0]["description"]["img"].element!.attributes["src"]
my code works ok and one item creates in table view correctly.
but the problem is when I want to get all values from xml file.
I couldn't create and array of the xml which is NSDATA type, not a string to be able to loop through it.
all I could manage to work is the below code which returns all xml tags value which I don't want that :
func enumerate(indexer: XMLIndexer, level: Int) {
for child in indexer.children {
let appName = child.element!.text
let appUrl = child.element!.attributes["src"]
let ap = Apps(name: appName , img : appUrl)
self.tableData.append(ap)
self.tableView.reloadData()
enumerate(child, level: level + 1)
}
}
enumerate(xml, level: 0)
Any Idea how to get those values in a loop without mistaking or getting other values?
here is my code :
let url = NSURL(string: "http://razavitv.aqr.ir/index/rss/2")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
let processedString = (dataString as NSString).stringByReplacingOccurrencesOfString("<![CDATA[", withString: "").stringByReplacingOccurrencesOfString("]]", withString: "") as String
let data: NSData = processedString.dataUsingEncoding(NSUTF8StringEncoding)!
let xml = SWXMLHash.parse(data)
// let appName = xml["rss"]["channel"]["item"][0]["title"].element!.text!
// let appUrl = xml["rss"]["channel"]["item"][0]["description"]["img"].element!.attributes["src"]
//
// let ap = Apps(name: appName , img : appUrl)
// self.tableData.append(ap)
//
// self.tableView.reloadData()
func enumerate(indexer: XMLIndexer, level: Int) {
for child in indexer.children {
let appName = child.element!.text
let appUrl = child.element!.attributes["src"]
let ap = Apps(name: appName , img : appUrl)
self.tableData.append(ap)
self.tableView.reloadData()
enumerate(child, level: level + 1)
}
}
enumerate(xml, level: 0)
}
task.resume()
very simple solution : just need to create a for loop with indexer inside.
here is the code :
//one root element
let count = xml["rss"]["channel"]["item"].all.count
for var i = 0; i < count; i++ {
let appName = xml["rss"]["channel"]["item"][i]["title"].element!.text!
let appUrl = xml["rss"]["channel"]["item"][i]["description"]["img"].element!.attributes["src"]
let ap = Apps(name: appName , img : appUrl)
self.tableData.append(ap)
self.tableView.reloadData()
}
Related
I have created save button which I guess going to create a .csv file to store x,y and name of the object on screen since I can't check it because I can't access the file its created
#IBAction func saveButton(_ sender: Any) {
let objectsName = stringObjectName.joined(separator: ",")
let coX = stringObjectX.joined(separator: ",")
let coY = stringObjectY.joined(separator: ",")
let fileName = getDocumentsDirectory().appendingPathComponent("output.csv")
CSVFilesName.append(fileName)
var csvText = "Name, Data X, Data Y\n"
let count = stringObjectName.count
if count > 0 {
for _ in 0..<(stringObjectName.count-1) {
let newLine = "\(objectsName),\(coX),\(coY)\n"
csvText.append(contentsOf: newLine)
}
}
do {
try csvText.write(to: fileName, atomically: true, encoding: String.Encoding.utf8)
} catch {
print("error")
}
print(fileName)
}
after this I try to access the file call "output.csv" which it is suppose to store in document directory in another viewport. So, I created the method to pass the method from another view controller to current view controller by using and store CSVFile
var CSVFileName = [URL]()
func assignArray() {
let cameraVC = CameraViewController()
CSVFileName = cameraVC.CSVFilesName
print(CSVFileName)
}
and the problem start here since I have array which suppose to store file name in String
let fileNames = ["sth1.csv", "sth2.csv"]
but I can't find the convert CSVFileName from saving button to String, and replace "static array" into "dynamic array" and change the method below to get
URL from FileManager().default.url instead of fileURL to give TableViewController data to show and accessible
private func prepareFileURLS() {
for file in fileNames {
let fileParts = file.components(separatedBy: ".")
print(fileParts[0])
if let fileURL = Bundle.main.url(forResource: fileParts[0], withExtension: fileParts[1]) {
if FileManager.default.fileExists(atPath: fileURL.path) {
print(fileURL)
fileURLs.append(fileURL as NSURL)
}
}
}
print(fileURLs)
}
Here is the way you can read Your CSV file :
func filterMenuCsvData()
{
do {
// This solution assumes you've got the file in your bundle
if let path = Bundle.main.path(forResource: "products_category", ofType: "csv"){
// STORE CONTENT OF FILE IN VARIABLE
let data = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
var rows : [String] = []
var readData = [String]()
rows = data.components(separatedBy: "\n")
for data in 0..<rows.count - 1{
if data == 0 || rows[data].contains(""){
continue
}
readData = rows[data].components(separatedBy: ",")
Category.append(readData[0])
if readData[2] != ""{
Occassions.append(readData[2])
}
selectedOccassionsRadioButtonIndex = Array(repeating: false, count: Occassions.count)
selectedCategoryRadioButtonIndex = Array(repeating: false, count: Category.count)
}
}
} catch let err as NSError {
// do something with Error}
print(err)
}
}
When I run the application Xcode told me that
unexpectedly found nil while unwrapping an Optional value
at the url but the url isn't nil, can someone help?
here is the code
import Foundation
protocol WeatherUndergroundServiceByGeographicalDelegate{
func setWeatherByGeographical(weather:WeatherUnderground)
}
class WeatherUndergoundServiceByGeographical{
var delegate:WeatherUndergroundServiceByGeographicalDelegate?
func getWeatherFromWeatherUnderground(latitude:Double, longitude:Double){
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude,longitude).json"
let url = NSURL(string: path)
//session
let session = NSURLSession.sharedSession()
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Error is at here~~~~~~~~~~~~~~~~~~~~~~~~~
let task = session.dataTaskWithURL(url!) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let json = JSON(data: data!)
//parsing json weather condition from weather api. using swiftyJson
let name = json["current_observation"]["display_location"]["city"].string
let temp = json["current_observation"]["temp_c"].double
let windsp = json["current_observation"]["wind_mph"].double
//prasing the weather data
let weather = WeatherUnderground(cityName: name!, temperature: temp!, windSpeed: windsp!)
if self.delegate != nil{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate?.setWeatherByGeographical(weather)
})
}
}
task.resume()
}
}
You probably have error in your path string, try this:
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude),\(longitude).json"
The reason is that you are interpolating tuple value \(latitude,longitude) in the string, which adds extra space and makes url string invalid because space is not percent-escaped.
Instead you have to interpolate each value with a comma between them: \(latitude),\(longitude)
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude,longitude).json"
I think you mean:
let path = "http://api.wunderground.com/api/48675fd2f5485cff/conditions/geolookup/q/\(latitude),\(longitude).json"
I'm facing a weird behavior in my IOs application, let me comment you a bit about it:
General method called from the UI
func GetSensorList(){
dispatch_async(dispatch_get_global_queue(priority, 0)) {
self.sensors = Sensor.GenerateSensorList()
dispatch_async(dispatch_get_main_queue()) {
self.collectionView?.reloadData()
}
}
}
I have this method that call a rest Web Service:
static func GenerateSensorList() -> [Sensor]{
var sensores = [Sensor]()
let manager: AppManager = AppManager.manager
var userData: UserData? = nil
do{
userData = try manager.GetUserData()
if let userDataAux = userData {
manager.SaveSharedUserData(userDataAux)
if(userDataAux.weatherSettings!.weatherCity != nil){
var unit = "imperial"
if(userDataAux.weatherSettings!.tempFormat! == "C"){
unit = "metric"
}
let s = userDataAux.weatherSettings!.weatherCity!.stringByReplacingOccurrencesOfString(" ", withString: "#")
let weather = try manager.GetWeatherData(s, metric: unit)
let temperatureInt = Int(weather.weatherMain!.temp!)
let description = weather.weatherItem![0].description
let temp = "Temp: " + String(temperatureInt) + "°" + (userData!.weatherSettings!.tempFormat!)
let sensorAux = Sensor(image: weather.image, label1: description , label2:temp)
sensores.append(sensorAux)
}
}
let deviceData = try? manager.RetrieveDeviceDataObject()
if(deviceData != nil){
if(deviceData!?.DeviceDataItems != nil){
let deviceDataItems = deviceData!?.DeviceDataItems!
for(var i = 0; i < deviceDataItems?.count; i++){
let catId = deviceDataItems![i].CategoryId
let devId = deviceDataItems![i].DeviceItemId
switch catId!{
case 12: break
case 16:
let devItem = try manager.GetDeviceHumiditySensorItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
var humidityValue = ""
if(devItem.DeviceItemHumiditySensorHumidity != nil){
humidityValue = (devItem.DeviceItemHumiditySensorHumidity)!
}else{
humidityValue = "0"
}
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ( humidityValue + "%"))
sensores.append(sensorAux)
case 17:
let devItem = try manager.GetDeviceTemperatureSensorItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ((devItem.DeviceItemTemperatureSensorTemperature)! + "°" + (userData!.weatherSettings!.tempFormat!)))
sensores.append(sensorAux)
case 18:
let devItem = try manager.GetDeviceLightSensorItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ((devItem.DeviceItemLightSensorLight)!))
sensores.append(sensorAux)
case 21:
let devItem = try manager.GetDevicePowerMeterItemById(devId!)
let photo1 = UIImage(named: "ic_devices")!
let sensorAux = Sensor(image: photo1, label1: devItem.DeviceItemName , label2: ((devItem.DeviceItemPowerMeterWatts)! + "Watts"))
sensores.append(sensorAux)
default: break
}
}
}
}
}
catch{
sensores = [Sensor]()
}
return sensores
}
In the line:
let weather = try manager.GetWeatherData(s, metric: unit)
I face the following issue: When I'm using the iPad emulator the method works fine, but when I'm using an iPhone 6 emulator I found that the data is different and the application crash.
I checked and the iPhone and the iPad is running the same version of IOs (9.2), the url is exactly the same, but the NSDATA object that I got are different.
GetWeatherData code:
func GetWeatherData(cityName: String, metric: String) throws -> WeatherCondition{
do{
var weather = WeatherCondition()
let url = SERVICEURL + "/GetWeather/" + cityName + "/" + metric
let data = try ExecuteRequestServiceHeader(url, mmsAuth: nil, mmsAuthSig: nil, mmsSession: nil)
if let dataAux = data{
let json = try NSJSONSerialization.JSONObjectWithData(dataAux, options: .MutableLeaves) as! NSDictionary
let jsonleave = json["GetWeatherResult"] as? String
if let jsonLeaveAux = jsonleave{
weather = WeatherCondition.JsonToObject(jsonLeaveAux)
let iconVar = weather.weatherItem![0].icon
let urlIcon = SERVICEURL + "/GetWeatherIcon/"+iconVar!
let dataIcon = try ExecuteRequestService(urlIcon)
if let dataIconAux = dataIcon{
let jsonIcon = try NSJSONSerialization.JSONObjectWithData(dataIconAux, options: .MutableLeaves) as! NSDictionary
if let jsonleaveIcon = jsonIcon["GetWeatherIconResult"] as? NSArray{
var byteArray = [UInt8]()
for (var i = 0; i < jsonleaveIcon.count; i++){
byteArray.append(UInt8(String(jsonleaveIcon[i]))!)
}
let imData = NSData(bytes: byteArray, length: (byteArray.count))
let image = UIImage(data: imData)
weather.image = image
}
}else{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
}else{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
}else{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
return weather
}catch{
throw AppManagerError.ErrorAccessingService(url: "Getting Weather data")
}
}
The application throw an exception in this line:
let json = try NSJSONSerialization.JSONObjectWithData(dataAux, options: .MutableLeaves) as! NSDictionary
Exception Track:
caught: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not
start with array or object and option to allow fragments not set."
UserInfo={NSDebugDescription=JSON text did not start with array or
object and option to allow fragments not set.}
I will appreciate any help on this
This was getting to complicated to write in a comment, so I will put this in answers.
First off, I am a bit confused by this part of your code:
do{
var weather = WeatherCondition()
let url = SERVICEURL + "/GetWeather/" + cityName + "/" + metric
let data = try ExecuteRequestServiceHeader(url, mmsAuth: nil, mmsAuthSig: nil, mmsSession: nil)
if let dataAux = data{
let json = try NSJSONSerialization.JSONObjectWithData(dataAux, options: .MutableLeaves) as! NSDictionary
You look like are you trying to create the dataAux variable based on trying to serialize dataAux not data. I would assume you want to call the JSONObjectWithData method on data. Not sure why you would see a difference on the different platforms, but that could be part of the problem.
In addition, it is worth calling the JSONObjectWithData method using .AllowFragments to see if this helps. I would also suggest taking doing something like this to see what the data actually looks like before you try to serialize it. At the very least it might help you trouble shoot the difference between the two platforms.
print(NSString(data: data, encoding: NSUTF8StringEncoding))
I try to parse json with SwiftyJSON. One of the fields have url to image and i try to save it as NSData but I face crash and console errors. Crash appears when compiler comes to object creation
code it the following
var JSONStorage : [Article?]?
var objects = [Article?]()
override func viewDidLoad() {
super.viewDidLoad()
let number = arc4random_uniform(1000)
let urlString = "http://wirehead.ru/article.json?\(number)"
if let url = NSURL(string: urlString) {
if let data = try? NSData(contentsOfURL: url, options: []) {
let json = JSON(data: data)
for element in json["article"].arrayValue {
let id = Int(element["id"].stringValue)
let title = element["title"].stringValue
let subtitle = element["subtitle"].stringValue
let body = element["body"].stringValue
let img = element["images"]["main"].rawValue
let obj:Article = Article(id: id!, title: title, subtitle: subtitle, body: body, mainImage: img as! NSData)
objects.append(obj)
print("We are inside if let")
}
}
}
print(objects)
}
Link to JSON is http://wirehead.ru/article.json and here is with highlight http://pastebin.com/AAEFjsQN
Error that I get is
Any advice ?
["images"]["main"] contains an URL represented by a String
To get the image data, use something like this
let imgURLString = element["images"]["main"].stringValue
if let url = NSURL(string:imgURLString) {
let img = NSData(contentsOfURL:url)
}
Is it possible to return multiple JSON files from a Content Blocker Extension? In my UI users enable / disable different filters and each filter is represented by a separate file. I currently have (which only loads one despite iterating through multiple):
func beginRequestWithExtensionContext(context: NSExtensionContext) {
var items = Array <NSExtensionItem>()
let resources = ["a", "b", "c"]
for resource in resources {
let url = NSBundle.mainBundle().URLForResource(resource, withExtension: "json")
if let attachment = NSItemProvider(contentsOfURL: url) {
let item = NSExtensionItem()
item.attachments = [attachment]
items.append(item)
}
}
context.completeRequestReturningItems(items, completionHandler: nil)
}
I've tried doing multiple items and a single item with multiple attachments. If it isn't possible to have separate files, any way to combine multiple (or generate programmatically)?
It is possible to have multiple JSON files and use it for the Content Blocker extension.
1) Throws SFContentBlockerErrorDomain when you pass multiple extension items to completeRequestReturningItems method.
2) Can't attach multiple attachments to NSExtension. The comment on the source code says, the attachment is not meant to be an array of alternate data formats/types, but instead a collection to include in a social media post for example. These items are always typed NSItemProvider. I reckon you wouldn't be able to add multiple JSON data as attachments, since they are not a series of attachments to create a message.
My Solution (Verified it works):
NSItemProvider can be initialised with item (NSData) and typeIdentifier.
let aData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("a", withExtension: "json")!)
let bData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("b", withExtension: "json")!)
aJSON = `convert aData to JSON`
bJSON = `convert bData to JSON`
combinedJSON = `aJSON + bJSON`
combinedData = 'convert combinedJSON to NSData'
let attachment = NSItemProvider(item: combinedData, typeIdentifier: kUTTypeJSON as String)
Now you could create the extension with the attachment, combinedData as per your preferences.
For those curious I ended up adding code to dynamically generate a JSON file (persisted to disk). From other answers it seems like the step of saving could be avoided by returning an NSData representation of the file instead - although that attempt failed for me. Here's my snippet:
import UIKit
import MobileCoreServices
class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequestWithExtensionContext(context: NSExtensionContext) {
let item = NSExtensionItem()
let items = [item]
let url = buildJSONFileURL()
if let attachment = NSItemProvider(contentsOfURL: url) { item.attachments = [attachment] }
context.completeRequestReturningItems(items, completionHandler: nil)
}
func buildJSONFileURL() -> NSURL {
let directories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let directory = directories[0]
let path = directory.stringByAppendingFormat("/block.json")
let selector = [...] // Dynamically Generated
let dictionary = [[
"action": [ "type": "css-display-none", "selector": selector ],
"trigger": [ "url-filter": ".*" ]
]]
let data = try! NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions.PrettyPrinted)
let text = NSString(data: data, encoding: NSASCIIStringEncoding)!
try! text.writeToFile(path, atomically: true, encoding: NSASCIIStringEncoding)
return NSURL(fileURLWithPath: path)
}
}
You can combine two JSON rule files in to one file and use that file.
import UIKit
import MobileCoreServices
class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
let sourceURLRules = sharedContainerURL?.appendingPathComponent("Rules1.json")
let sourceURLRules2 = sharedContainerURL?.appendingPathComponent("Rules2.json")
do {
let jsonDecoder = JSONDecoder()
let dataFormRules1 = try Data(contentsOf: sourceURLRules1!, options: .mappedIfSafe)// Rule is Decode able Swift class
let rulesArray1 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules1)
let dataFormRules2 = try Data(contentsOf: sourceURLRules2!, options: .mappedIfSafe)
let rulesArray2 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules2)
saveCombinedRuleFile(ruleList: rulesArray1! + rulesArray2!)
} catch {
//handle error condition
}
let sourceURLCombinedRule = sharedContainerURL?.appendingPathComponent("CombinedRule.json")
let combinedRuleAttachment = NSItemProvider(contentsOf: sourceURLCombinedRule)
let item = NSExtensionItem()
item.attachments = [combinedRuleAttachment]
context.completeRequest(returningItems: [item], completionHandler: nil)
}
func saveCombinedRuleFile(ruleList:[Rule]) {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(ruleList) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
if let destinationURL = sharedContainerURL?.appendingPathComponent("CombinedRule.json") {
do {
try encoded.write(to: destinationURL)
} catch {
print ("catchtry")
}
}
}
}
}