I'm designing an iOS App using Swift. I have a View Controller, DisplayBrand that's displaying data fetched from a backend. I have made another class, FetchAndParseBrand, for fetching and parsing data from the backend.
The view controller is calling a method, getBrand, in the fetching class. This method then revieces data asynchronously from the backend.
How do I return the received data to the View Controller? I guess there are multiple ways of solving this problem, do you have any suggestion for the best practice?
Here is some code for context:
DisplayBrand
class DisplayBrand: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var brand = Brand()
FetchAndParseBrand().getBrand()
self.updateUI()
}
...
}
FetchAndParseBrand:
class FetchAndParseBrand: NSObject {
// Fetches one brand on static url
func getBrand() -> Brand{
let qos = Int(QOS_CLASS_USER_INITIATED.value)
var jsonError: NSError?
var brand = Brand()
if let url = NSURL(string: "http://backend.com/api/brand")
{
dispatch_async(dispatch_get_global_queue(qos, 0))
{
// simulate long load delay
println("going to sleep")
sleep(2)
if let brandData = NSData(contentsOfURL: url)
{
dispatch_async(dispatch_get_main_queue())
{
if let jsonBrand = NSJSONSerialization.JSONObjectWithData(brandData, options: nil, error: &jsonError) as? NSDictionary {
if let newName = jsonBrand.valueForKey("Name") as? String {
brand.name = newName
}
if let newGender = jsonBrand.valueForKey("Gender") as? Int {
if newGender == 0 {brand.gender = "male"}
else if newGender == 1 {brand.gender = "female"}
}
if let newId = jsonBrand.valueForKey("Id") as? Int {
brand.id = newId
}
brand.printBrand()
}
}
}
}
}
return brand
}
}
Brand
class Brand: NSObject {
var id: Int?
var name: String?
var image: UIImage?
var gender: String?
func printBrand() {
if id != nil { println("ID:\t\t\(self.id!)") }
if name != nil { println("Name:\t\(self.name!)") }
if image != nil { println("Image:\t\(self.image?.description)") }
if gender != nil { println("Gender:\t\(self.gender!)") }
}
}
You can use a completion handler for this, this will return the variable when it is read:
class FetchAndParseBrand: NSObject {
class func getBrand(completion: (brand: Brand) -> ()) {
let qos = Int(QOS_CLASS_USER_INITIATED.value)
var jsonError: NSError?
var brand = Brand()
if let url = NSURL(string: "http://backend.com/api/brand")
{
dispatch_async(dispatch_get_global_queue(qos, 0))
{
// simulate long load delay
println("going to sleep")
sleep(2)
if let brandData = NSData(contentsOfURL: url)
{
dispatch_async(dispatch_get_main_queue())
{
if let jsonBrand = NSJSONSerialization.JSONObjectWithData(brandData, options: nil, error: &jsonError) as? NSDictionary {
if let newName = jsonBrand.valueForKey("Name") as? String {
brand.name = newName
}
if let newGender = jsonBrand.valueForKey("Gender") as? Int {
if newGender == 0 {brand.gender = "male"}
else if newGender == 1 {brand.gender = "female"}
}
if let newId = jsonBrand.valueForKey("Id") as? Int {
brand.id = newId
}
completion(brand: brand)
brand.printBrand()
}
}
}
}
}
}
}
Then you can call the function like so:
FetchAndParseBrand.getBrand { (brand) -> () in
println(brand)
}
there are many ways. with callback function and delegates. you can also use singleton if you will call back only to a specific view controller:
class DisplayBrand: UIViewController {
static var sharedInstance :DisplayBrand?
override func viewDidLoad() {
super.viewDidLoad()
DisplayBrand.sharedInstance = self;
}
.
.
.
//on async over:
displayBrand.sharedInstance?.someFunction();
There is a major issue in your code:
FetchAndParseBrand.getBrand() is supposed to be a class method and has no parameter
The easiest way to get a asynchronous callback is a closure
Try this
Class DisplayBrand
class DisplayBrand: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
FetchAndParseBrand.getBrand() { (brand) -> () in
// do something with variable brand
self.updateUI() }
}
...
}
Class FetchAndParseBrand
class FetchAndParseBrand: NSObject {
// Fetches one brand on static url
class func getBrand(completion: (Brand)->()) {
let qos = Int(QOS_CLASS_USER_INITIATED.value)
var jsonError: NSError?
var brand = Brand()
if let url = NSURL(string: "http://backend.com/api/brand")
{
dispatch_async(dispatch_get_global_queue(qos, 0))
{
// simulate long load delay
println("going to sleep")
sleep(2)
if let brandData = NSData(contentsOfURL: url)
{
dispatch_async(dispatch_get_main_queue())
{
if let jsonBrand = NSJSONSerialization.JSONObjectWithData(brandData, options: nil, error: &jsonError) as? NSDictionary {
if let newName = jsonBrand.objectForKey("Name") as? String {
brand.name = newName
}
if let newGender = jsonBrand.objectForKey("Gender") as? Int {
if newGender == 0 {brand.gender = "male"}
else if newGender == 1 {brand.gender = "female"}
}
if let newId = jsonBrand.objectForKey("Id") as? Int {
brand.id = newId
}
brand.printBrand()
completion(brand)
}
}
}
}
}
}
}
Side note: valueForKey is a key value coding method. The standard method to get a value from a dictionary by key is objectForKey or use subscripting
Related
I have a set up, using Ashley Mills Reachability, which is supposed to send a notification, using NotificationCenter, to the application when the connection of the app changes. It is set up as follows:
func reachabilityChanged(note: Notification) {
let reachability = note.object as! Reachability
switch reachability.connection {
case .wifi:
print("Reachable via WiFi")
case .cellular:
print("Reachable via Cellular")
case .none:
print("Network not reachable")
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.reachabilityChanged), name: .reachabilityChanged, object: reachability)
do{
try reachability.startNotifier()
}catch{
print("could not start reachability notifier")
}
}
With my computer (which is what I am running the simulator on) connected to wifi, the console accurately prints "Reachable via Wifi". Turning off the wifi causes the console to, again, accurately print "Network not reachable". I run into an issue, though, when I turn the wifi back on. "Reachable via Wifi" does not get printed to the log... in fact, nothing gets printed to the log. I do not know why this is happening nor how to fix it. Anyone have any ideas?
This is the reachability code:
import SystemConfiguration
import Foundation
public enum ReachabilityError: Error {
case FailedToCreateWithAddress(sockaddr_in)
case FailedToCreateWithHostname(String)
case UnableToSetCallback
case UnableToSetDispatchQueue
}
#available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
extension Notification.Name {
public static let reachabilityChanged = Notification.Name("reachabilityChanged")
}
func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
guard let info = info else { return }
let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
reachability.reachabilityChanged()
}
public class Reachability {
public typealias NetworkReachable = (Reachability) -> ()
public typealias NetworkUnreachable = (Reachability) -> ()
#available(*, unavailable, renamed: "Conection")
public enum NetworkStatus: CustomStringConvertible {
case notReachable, reachableViaWiFi, reachableViaWWAN
public var description: String {
switch self {
case .reachableViaWWAN: return "Cellular"
case .reachableViaWiFi: return "WiFi"
case .notReachable: return "No Connection"
}
}
}
public enum Connection: CustomStringConvertible {
case none, wifi, cellular
public var description: String {
switch self {
case .cellular: return "Cellular"
case .wifi: return "WiFi"
case .none: return "No Connection"
}
}
}
public var whenReachable: NetworkReachable?
public var whenUnreachable: NetworkUnreachable?
#available(*, deprecated: 4.0, renamed: "allowsCellularConnection")
public let reachableOnWWAN: Bool = true
/// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
public var allowsCellularConnection: Bool
// The notification center on which "reachability changed" events are being posted
public var notificationCenter: NotificationCenter = NotificationCenter.default
#available(*, deprecated: 4.0, renamed: "connection.description")
public var currentReachabilityString: String {
return "\(connection)"
}
#available(*, unavailable, renamed: "connection")
public var currentReachabilityStatus: Connection {
return connection
}
public var connection: Connection {
guard isReachableFlagSet else { return .none }
// If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
guard isRunningOnDevice else { return .wifi }
var connection = Connection.none
if !isConnectionRequiredFlagSet {
connection = .wifi
}
if isConnectionOnTrafficOrDemandFlagSet {
if !isInterventionRequiredFlagSet {
connection = .wifi
}
}
if isOnWWANFlagSet {
if !allowsCellularConnection {
connection = .none
} else {
connection = .cellular
}
}
return connection
}
fileprivate var previousFlags: SCNetworkReachabilityFlags?
fileprivate var isRunningOnDevice: Bool = {
#if (arch(i386) || arch(x86_64)) && os(iOS)
return false
#else
return true
#endif
}()
fileprivate var notifierRunning = false
fileprivate let reachabilityRef: SCNetworkReachability
fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability")
required public init(reachabilityRef: SCNetworkReachability) {
allowsCellularConnection = true
self.reachabilityRef = reachabilityRef
}
public convenience init?(hostname: String) {
guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
self.init(reachabilityRef: ref)
}
public convenience init?() {
var zeroAddress = sockaddr()
zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
zeroAddress.sa_family = sa_family_t(AF_INET)
guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
self.init(reachabilityRef: ref)
}
deinit {
stopNotifier()
}
}
public extension Reachability {
// MARK: - *** Notifier methods ***
func startNotifier() throws {
guard !notifierRunning else { return }
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
stopNotifier()
throw ReachabilityError.UnableToSetCallback
}
if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
stopNotifier()
throw ReachabilityError.UnableToSetDispatchQueue
}
// Perform an initial check
reachabilitySerialQueue.async {
self.reachabilityChanged()
}
notifierRunning = true
}
func stopNotifier() {
defer { notifierRunning = false }
SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
}
// MARK: - *** Connection test methods ***
#available(*, deprecated: 4.0, message: "Please use `connection != .none`")
var isReachable: Bool {
guard isReachableFlagSet else { return false }
if isConnectionRequiredAndTransientFlagSet {
return false
}
if isRunningOnDevice {
if isOnWWANFlagSet && !reachableOnWWAN {
// We don't want to connect when on cellular connection
return false
}
}
return true
}
#available(*, deprecated: 4.0, message: "Please use `connection == .cellular`")
var isReachableViaWWAN: Bool {
// Check we're not on the simulator, we're REACHABLE and check we're on WWAN
return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
}
#available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
var isReachableViaWiFi: Bool {
// Check we're reachable
guard isReachableFlagSet else { return false }
// If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
guard isRunningOnDevice else { return true }
// Check we're NOT on WWAN
return !isOnWWANFlagSet
}
var description: String {
let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X"
let R = isReachableFlagSet ? "R" : "-"
let c = isConnectionRequiredFlagSet ? "c" : "-"
let t = isTransientConnectionFlagSet ? "t" : "-"
let i = isInterventionRequiredFlagSet ? "i" : "-"
let C = isConnectionOnTrafficFlagSet ? "C" : "-"
let D = isConnectionOnDemandFlagSet ? "D" : "-"
let l = isLocalAddressFlagSet ? "l" : "-"
let d = isDirectFlagSet ? "d" : "-"
return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
}
}
fileprivate extension Reachability {
func reachabilityChanged() {
guard previousFlags != flags else { return }
let block = connection != .none ? whenReachable : whenUnreachable
DispatchQueue.main.async {
block?(self)
self.notificationCenter.post(name: .reachabilityChanged, object:self)
}
previousFlags = flags
}
var isOnWWANFlagSet: Bool {
#if os(iOS)
return flags.contains(.isWWAN)
#else
return false
#endif
}
var isReachableFlagSet: Bool {
return flags.contains(.reachable)
}
var isConnectionRequiredFlagSet: Bool {
return flags.contains(.connectionRequired)
}
var isInterventionRequiredFlagSet: Bool {
return flags.contains(.interventionRequired)
}
var isConnectionOnTrafficFlagSet: Bool {
return flags.contains(.connectionOnTraffic)
}
var isConnectionOnDemandFlagSet: Bool {
return flags.contains(.connectionOnDemand)
}
var isConnectionOnTrafficOrDemandFlagSet: Bool {
return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
}
var isTransientConnectionFlagSet: Bool {
return flags.contains(.transientConnection)
}
var isLocalAddressFlagSet: Bool {
return flags.contains(.isLocalAddress)
}
var isDirectFlagSet: Bool {
return flags.contains(.isDirect)
}
var isConnectionRequiredAndTransientFlagSet: Bool {
return flags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
}
var flags: SCNetworkReachabilityFlags {
var flags = SCNetworkReachabilityFlags()
if SCNetworkReachabilityGetFlags(reachabilityRef, &flags) {
return flags
} else {
return SCNetworkReachabilityFlags()
}
}
}
it get's triggered only for one time per run in the simulator, but on a real iOS device, it works normally.
let's assume that my model has the following:
import Foundation
class Model {
var userName = [String]()
var userAnswer = [String]()
var userInfo = [String]()
var name:String
var answer:String
init(){
self.name = ""
self.answer = ""
}
func addUserInfo(name:String, answer:String) -> Void {
userName.append(name)
userAnswer.append(answer)
}
}
What would one do to transform this model to have userName, and userAnswer become one joined dictionary? where the function addUserInfo would still work. Thanks!
you can use a funcion return a array or a new property with only get methord,just like this
var userNamesAndUserAnswers:[[String:String]]
{
get
{
var values:[[String:String]]=[]
for index in 0..<userName.count
{
values.append(["name":userName[index],"answer":userAnswer[index]])
}
return values
}
}
You can just have a Dictionary that represents user names mapped to user answers like this:
class Model {
var userInfo = [String:String]()
func addUserInfo(name:String, answer:String) {
userInfo[name] = answer
}
}
There's no need to import Foundation since you're not using any Foundation objects.
If you want a [String: String]:
var dict: [String: String] {
var result: [String: String] = [:]
zip(userName, userAnswer).forEach { result[$0.0] = $0.1 }
return result
}
Or if you want [[String: String]]:
var dict2: [[String: String]] { return zip(userName, userAnswer).map { [$0.0: $0.1] } }
In the new Contacts framework there appears to be a way to search by name:
let predicate = CNContact.predicateForContactsMatchingName("john")
let toFetch = [CNContactGivenNameKey, CNContactFamilyNameKey]
do {
let contacts = try store.unifiedContactsMatchingPredicate(
predicate, keysToFetch: toFetch)
for contact in contacts{
print(contact.givenName)
print(contact.familyName)
print(contact.identifier)
}
}
catch let err {
print(err)
}
But no apparent way to search by e-mail that I can find in the documentation or searches.
How do I search contacts by e-mail address?
These articles here and here have been helpful in learning the new framework but neither of these have revealed how to search by e-mail.
Having the same problem, I managed to find a solution. I have solved this by fetching all the contacts and then iterating over them to find matching contacts.
The code can surely be refactored.
import UIKit
import Contacts
public class ContactFinder: NSObject {
private lazy var store = CNContactStore()
private var allContacts: [CNContact] = []
private let keysToFetch: [CNKeyDescriptor] = [CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPostalAddressesKey, CNContactBirthdayKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
public func requestAccess(completion:((success: Bool, error: NSError?)->())) {
let status = CNContactStore.authorizationStatusForEntityType(.Contacts)
if status == .NotDetermined {
store.requestAccessForEntityType(.Contacts, completionHandler: { [weak self](success, error) -> Void in
if success {
self?.getAllContacts(completion)
} else {
completion(success: false, error: error)
}
})
} else if status == .Authorized {
getAllContacts(completion)
} else {
completion(success: false, error: nil)
}
}
public func searchForContactUsingEmail(email: String, completion:((contacts: [CNContact])->())) {
var contacts: [CNContact] = []
for contact in allContacts {
let em = contact.emailAddresses
if em.count > 0 {
let results = em.filter({ val in
let ce = (val.value as! String).trimmedString.lowercaseString
return ce == email.trimmedString.lowercaseString
})
if results.count > 0 {
contacts.append(contact)
}
}
}
completion(contacts: contacts)
}
private func getAllContacts(completion:((success: Bool, error: NSError?)->())) {
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
request.predicate = nil
request.unifyResults = true
do {
try store.enumerateContactsWithFetchRequest(request, usingBlock: { [weak self](contact, _) -> Void in
self?.allContacts.append(contact)
})
} catch let e as NSError {
print(e.localizedDescription)
completion(success: false, error: e)
return
}
completion(success: true, error: nil)
}
}
here's my code:
public class Payment: NSObject, NSXMLParserDelegate {
public var buchungsdatum:NSDate
public var valutadatum:NSDate
public var betrag:Double
public var verwendungszweck:String
public var creditorIban:String
public var creditorBic:String
public var creditorName:String
public var debitorIban:String
public var debitorBic:String
public var debitorName:String
override init() {
buchungsdatum = NSDate()
valutadatum = NSDate()
betrag = 0.0
verwendungszweck = ""
creditorIban = ""
creditorBic = ""
creditorName = ""
debitorBic = ""
debitorIban = ""
debitorName = ""
}
override public var description: String {
return "Payment-Information:"
}
func beginParse() {
let url = NSURL(fileURLWithPath: "/Users/Andi/Downloads/c53/CAMT/2015-10-06_C53_DE92300606010303502481_EUR_585142.xml")
let xml = NSXMLParser(contentsOfURL: url)!
xml.delegate = self
xml.parse()
}
#objc public func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
print("get an element")
}
#objc public func parserDidStartDocument(parser: NSXMLParser) {
print("started")
}
#objc public func parserDidEndDocument(parser: NSXMLParser) {
print("ended")
}
public func parser(parser: NSXMLParser, parseErrorOccurred parseError: NSError) {
print("Fehler")
}
}
var aPayment:Payment = Payment()
aPayment.beginParse()
Can anyone tell me why the parse() returns false and none of the delegate methods get called. Even the parseError one doesn't. I absolutely don't understand why.
Xcode even shows that the XML-File has content and that url isn't nil (see pic)
Xcode shows the XMLContent in the playground
It is most likely because your NSURL is nil.
This is my query code from parse:
func queryFromParse(){
//var query = PFQuery(className: "Posts")
var query = PFQuery(className: "currentUploads")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil
{
if let newObjects = objects as? [PFObject] {
for oneobject in newObjects {
let text = oneobject["imageText"] as! String
let username = oneobject["username"] as! String
let time = oneobject.createdAt!
let userImage = oneobject["imageFile"] as! PFFile
let imageURL = userImage.url // <- Bruker nå userImage.URL, henter ikke bildefilen med en gang
var OneBigObject = Details(username: username, text: text, CreatedAt: time, image: imageURL!)
self.arrayOfDetails.append(OneBigObject)
dispatch_async(dispatch_get_main_queue()) { self.collectionView.reloadData() }
}
}
}
}
}
I get error in code:
let userImage = oneobject["imageFile"] as! PFFile
fatal error: unexpectedly found nil while unwrapping an Optional value
I know why, because one of the rows in parse does not contains image:
Any ideas? What can I do to not get crash if there is not image there?
Here you go, try something likt this:
func queryFromParse(){
var query = PFQuery(className: "currentUploads")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil
{
if let newObjects = objects as? [PFObject] {
for oneobject in newObjects {
let text = oneobject["imageText"] as! String
let username = oneobject["username"] as! String
let time = oneobject.createdAt!
if let userImage = oneobject["imageFile"] as? PFFile {
let userImage = oneobject["imageFile"] as! PFFile
let imageURL = userImage.url // <- Bruker nå userImage.URL, henter ikke bildefilen med en gang
var OneBigObject = Details(username: username, text: text, CreatedAt: time, image: imageURL!)
self.arrayOfDetails.append(OneBigObject)
dispatch_async(dispatch_get_main_queue()) { self.collectionView.reloadData() }
}
}
}
}
}
}
optionally unwrap that object:
let imageFile = oneobject["imageFile"] as PFFile
imageFile.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
}
}
}
try use this
if let userImage = oneobject["imageFile"] as? PFFile {
let imageURL = userImage.url
}