When should we clear Core Data cache entity? - swift

I am using code in tutorial by http://www.raywenderlich.com/76735/using-nsurlprotocol-swift
In that tutorial the code is caching every single request.
My question is when and how can I clear entity of caching (Core data)? Or We dont need to clear caching entity?
Caching entity is entity where I save all data requested by NSConnection. If we need to clear cache in core data how can I choose some data to stay and others to be deleted.
For example , I am making news app. Every VC has label such as title, date, category and UIWebView. And when app is closed I want to delete all caching links except "saved news". "Saved news" is category which users choose by tapping button "plus".
I am thinking to create new column named "isToSave". So, when user taps button to save. I need to parse content text and pull out links (src=) and in CoreData set the column "isToSave "to true. When app is closed I will delete all links with column "isTosave" = false
So shortly: is it good practice to clear entity "cacheURls" and if yes how can I clear urls by selection ?
Here is code:
import UIKit
import CoreData
var requestCount = 0
class MyURLProtocol: NSURLProtocol {
var connection: NSURLConnection!
var mutableData: NSMutableData!
var response: NSURLResponse!
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
println("Request #\(requestCount++): URL = \(request.URL.absoluteString)")
if NSURLProtocol.propertyForKey("MyURLProtocolHandledKey", inRequest: request) != nil {
return false
}
return true
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
return request
}
override class func requestIsCacheEquivalent(aRequest: NSURLRequest,
toRequest bRequest: NSURLRequest) -> Bool {
return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
}
override func startLoading() {
// 1
let possibleCachedResponse = self.cachedResponseForCurrentRequest()
if let cachedResponse = possibleCachedResponse {
println("Serving response from cache")
// 2
let data = cachedResponse.valueForKey("data") as NSData!
let mimeType = cachedResponse.valueForKey("mimeType") as String!
let encoding = cachedResponse.valueForKey("encoding") as String!
// 3
let response = NSURLResponse(URL: self.request.URL, MIMEType: mimeType, expectedContentLength: data.length, textEncodingName: encoding)
// 4
self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
self.client!.URLProtocol(self, didLoadData: data)
self.client!.URLProtocolDidFinishLoading(self)
} else {
// 5
println("Serving response from NSURLConnection")
var newRequest = self.request.mutableCopy() as NSMutableURLRequest
NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)
self.connection = NSURLConnection(request: newRequest, delegate: self)
}
}
override func stopLoading() {
if self.connection != nil {
self.connection.cancel()
}
self.connection = nil
}
func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
self.response = response
self.mutableData = NSMutableData()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
self.client!.URLProtocol(self, didLoadData: data)
self.mutableData.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
self.client!.URLProtocolDidFinishLoading(self)
self.saveCachedResponse()
}
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
self.client!.URLProtocol(self, didFailWithError: error)
}
func saveCachedResponse () {
println("Saving cached response")
// 1
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
let context = delegate.managedObjectContext!
// 2
let cachedResponse = NSEntityDescription.insertNewObjectForEntityForName("CachedURLResponse", inManagedObjectContext: context) as NSManagedObject
cachedResponse.setValue(self.mutableData, forKey: "data")
cachedResponse.setValue(self.request.URL.absoluteString, forKey: "url")
cachedResponse.setValue(NSDate(), forKey: "timestamp")
cachedResponse.setValue(self.response.MIMEType, forKey: "mimeType")
cachedResponse.setValue(self.response.textEncodingName, forKey: "encoding")
// 3
var error: NSError?
let success = context.save(&error)
if !success {
println("Could not cache the response")
}
}
func cachedResponseForCurrentRequest() -> NSManagedObject? {
// 1
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
let context = delegate.managedObjectContext!
// 2
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("CachedURLResponse", inManagedObjectContext: context)
fetchRequest.entity = entity
// 3
let predicate = NSPredicate(format:"url == %#", self.request.URL.absoluteString!)
fetchRequest.predicate = predicate
// 4
var error: NSError?
let possibleResult = context.executeFetchRequest(fetchRequest, error: &error) as Array<NSManagedObject>?
// 5
if let result = possibleResult {
if !result.isEmpty {
return result[0]
}
}
return nil
}
}

Related

In-App Purchase invalid identifier during review(works fine while in development)

i am working on an app that has a non-consumable product. I have tested with multiple sandbox accounts and everything works fine during development. but during the app review, the productid are being returned as invalid and i can't get past the review. i make sure that the non-consumable product is in "waiting for review" state just before i submit the app for review. also i have the latest provisioning profile setup in xcode. i already got rejected twice, any ideas on what might be causing the issue will be greatly appreciated.
In appDelegate you set put below code in appDelegate and import svprogresshud pod for handle error
let PRODUCT_ID = "your itunes bundal identifire for IN app purrchase"
func fetchProductPrice(){
InAppPurchaseManager.sharedManager.fetchProductPrice(productId: PRODUCT_ID) { (productInfo, price) in
print("price : \(price)")
}
}
InAppPurchaseManager
import UIKit
import StoreKit
import SVProgressHUD
var productTitle:String = ""
var productPrice:String = ""
struct InAppMessages {
static let kUserNotAuthorizedMessage = "You are not authorize to make purchase."
static let kProductNotAvailableMessage = "Product not available."
static let kErrorConnectingToServer = "Error in connecting to iTunes server."
static let kErrorInFetchProductInfoMessage = "Failed to get product information."
static let kProductPurchaseFailed = "Failed to purchase product.";
}
var iTuneURL = "https://buy.itunes.apple.com/verifyReceipt"
class InAppPurchaseManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
typealias PurchaseFailureHandler = (_ error : String) -> ()
typealias PurchaseSuccessHandler = (_ transcation : SKPaymentTransaction) -> ()
typealias ReceiptFailureHandler = (_ error : String) -> ()
typealias ReceiptSuccessHandler = (_ receiptData : AnyObject) -> ()
typealias RestoreSuccessHandler = (_ restoreProductID : [String]) -> ()
typealias RestoreFailureHandler = (_ error : String) -> ()
typealias ProductFetchHandler = (_ productInfo : String, _ price: String) -> ()
static let sharedManager = InAppPurchaseManager()
var purchaseFailureHandler : PurchaseFailureHandler?
var purchaseSuccessHandler : PurchaseSuccessHandler?
var receiptFailureHandler : ReceiptFailureHandler?
var receiptSuccessHandler : ReceiptSuccessHandler?
var restoreSuccessHandler : RestoreSuccessHandler?
var restoreFailureHandler : RestoreFailureHandler?
var productFetchHandler : ProductFetchHandler?
var productToBuy : SKProduct?
var productID : String?
// #if DEBUG
// let iTuneURL = "https://sandbox.itunes.apple.com/verifyReceipt"
// #else
// #endif
override init() {
super.init()
//Add Payment Queue Transaction Observer
SKPaymentQueue.default().add(self)
}
//==========================================
//MARK: - In App Purchase Handler Methods
//==========================================
func finishInterruptedTransactionsWithSuccess(_ successHandler :#escaping PurchaseSuccessHandler, _ failureHandler : #escaping PurchaseFailureHandler){
purchaseSuccessHandler = successHandler
purchaseFailureHandler = failureHandler
let currentQueue : SKPaymentQueue = SKPaymentQueue.default()
print("Transactions: \(currentQueue.transactions.count)")
for transaction in currentQueue.transactions {
if (transaction.transactionState == SKPaymentTransactionState.failed) {
//possibly handle the error
currentQueue.finishTransaction(transaction);
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductPurchaseFailed)
purchaseFailureHandler = nil
} else if (transaction.transactionState == SKPaymentTransactionState.purchased) {
//deliver the content to the user
currentQueue.finishTransaction(transaction)
purchaseSuccessHandler?(transaction)
purchaseSuccessHandler = nil
} else {
//handle other transaction states
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductPurchaseFailed)
purchaseFailureHandler = nil
}
}
}
func canPurchase() -> Bool{
return SKPaymentQueue.canMakePayments()
}
func buyProduct(){
// show progress
self.showMessageIndicator(message: "Please wait...")
//AppData.showProgressGIFImage()
let payment = SKPayment(product: self.productToBuy!)
SKPaymentQueue.default().add(payment);
}
func fetchProductPrice(productId: String, fetchHandler : #escaping ProductFetchHandler){
productFetchHandler = fetchHandler
productID = productId
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet = NSSet(object: productId);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
}
}
func purchaseProductWithProductId(productId : String, fetchHandler : #escaping ProductFetchHandler ,successHandler : #escaping PurchaseSuccessHandler, failureHandler : #escaping PurchaseFailureHandler){
purchaseSuccessHandler = successHandler
purchaseFailureHandler = failureHandler
productFetchHandler = fetchHandler
productID = productId
if (SKPaymentQueue.canMakePayments())
{
// show indicator
self.showMessageIndicator(message:"Getting Product Information...")
let productID:NSSet = NSSet(object: productId);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
}else{
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kErrorConnectingToServer)
// show error
// self.showErrorAlert(error: InAppMessages.kErrorConnectingToServer)
}
}
func restoreProduct(successHandler : #escaping RestoreSuccessHandler, failureHandler : #escaping RestoreFailureHandler){
// show indicator
self.showMessageIndicator(message:"Restoring purchase...")
restoreSuccessHandler = successHandler
restoreFailureHandler = failureHandler
SKPaymentQueue.default().restoreCompletedTransactions()
}
//==========================================
//MARK: - SKPayment Request Delegate Methods
//==========================================
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("resposen invalid product \(response.invalidProductIdentifiers)")
let count : Int = response.products.count
if (count>0) {
let validProduct: SKProduct = response.products[0]
if (validProduct.productIdentifier == productID) {
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
numberFormatter.numberStyle = NumberFormatter.Style.currency
numberFormatter.locale = validProduct.priceLocale
let formattedPrice = numberFormatter.string(from: validProduct.price)
print("formattedPrice: \(formattedPrice!)")
productPrice = formattedPrice!
hideIndicator()
self.productToBuy = validProduct
productTitle = validProduct.localizedTitle
//let productMessage = "Do you want to buy \(validProduct.localizedTitle) at \(formattedPrice!) amount?"
let productMessage = " Want to access premium content for only \(formattedPrice!) a month?"
productFetchHandler?(productMessage, formattedPrice!)
// buy product
//buyProduct(product: validProduct);
}
else {
SVProgressHUD.dismiss()
print(validProduct.productIdentifier)
purchaseFailureHandler?(InAppMessages.kProductNotAvailableMessage)
//self.showErrorAlert(error: InAppMessages.kProductNotAvailableMessage)
}
} else {
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductNotAvailableMessage)
// show error
// self.showErrorAlert(error: InAppMessages.kProductNotAvailableMessage)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Error Fetching product information : \(error.localizedDescription)");
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kErrorInFetchProductInfoMessage)
// show error alert
// self.showErrorAlert(error: InAppMessages.kErrorInFetchProductInfoMessage)
}
//============================================
//MARK: - SKPaymentTransactionObserver Methods
//============================================
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
//print("Received Payment Transaction Response from Apple");
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
// hide indicator
self.hideIndicator()
//print("Product Purchased: \(transaction.transactionIdentifier)")
SKPaymentQueue.default().finishTransaction(transaction);
self.purchaseSuccessHandler?(transaction)
self.purchaseSuccessHandler = nil
//To Get Receipt from Bundle
// let mainBundle = Bundle.main as Bundle;
// let receiptUrl = mainBundle.appStoreReceiptURL;
//
// let data = NSData(contentsOf: receiptUrl!);
//
// if(data != nil){
//
// let base64String = data!.base64EncodedString()
//
// // verify payment receipt
// verifyPaymentReceipt(base64EncodedString: base64String)
// }
break;
case .failed:
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductPurchaseFailed)
purchaseFailureHandler = nil
SKPaymentQueue.default().finishTransaction(transaction);
SVProgressHUD.dismiss()
//self.showErrorAlert(error: InAppMessages.kProductPurchaseFailed)
break;
case .purchasing:
self.showMessageIndicator(message: "Please wait \ntransaction in progress...")
print("Transaction is being added to the server queue.");
case .restored:
SKPaymentQueue.default().finishTransaction(transaction);
print("Already Purchased");
case .deferred:
print("The transaction is in the queue, but its final status is pending external action.");
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("Restore Purchase done")
var productIds = [String]()
for transcation in queue.transactions{
productIds.append(transcation.payment.productIdentifier)
}
hideIndicator()
self.restoreSuccessHandler?(productIds)
self.restoreSuccessHandler = nil
self.restoreFailureHandler = nil
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error){
self.restoreFailureHandler?(error.localizedDescription)
self.restoreSuccessHandler = nil
self.restoreFailureHandler = nil
}
//============================================
//MARK: - Verify Receipt
//============================================
func verifyPaymentReceipt(base64EncodedString : String,ReceiptValidationPassword : String, receiptSuccess : ReceiptSuccessHandler?, receiptFailure : ReceiptFailureHandler?){
receiptSuccessHandler = receiptSuccess
receiptFailureHandler = receiptFailure
self.showMessageIndicator(message: "Verifying payment...")
// Create the JSON object that describes the request
let requestContents = NSMutableDictionary();
requestContents.setObject(base64EncodedString, forKey: "receipt-data" as NSCopying);
requestContents.setObject(ReceiptValidationPassword, forKey: "password" as NSCopying);
var requestData : NSData?
do{
requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions()) as NSData?;
}catch{
NSLog("Error in json data creation at verifyPaymentReceipt");
}
StoreDataCheck(requestData!)
}
func StoreDataCheck(_ requestData:NSData)
{
// Create a POST request with the receipt data.
let storeURL = NSURL(string: iTuneURL);
var storeRequest = URLRequest(url: storeURL! as URL);
storeRequest.httpMethod = "POST";
storeRequest.httpBody = requestData as Data?;
let session = URLSession(configuration: URLSessionConfiguration.default)
let dataTask = session.dataTask(with: storeRequest) { (responseData, response, error) in
// self.hideIndicator()
if (error == nil){
var jsonResponse: NSDictionary = NSDictionary()
do{
jsonResponse = try JSONSerialization.jsonObject(with: responseData!, options: JSONSerialization.ReadingOptions.allowFragments) as! NSDictionary
if jsonResponse.value(forKey: "status") as? Int == 21007
{
iTuneURL = "https://sandbox.itunes.apple.com/verifyReceipt"
self.StoreDataCheck(requestData)
}
else
{
self.receiptSuccessHandler?(jsonResponse)
}
}catch{
self.receiptFailureHandler?("Parsing issue : verifyPaymentReceipt")
}
}
else{
self.receiptFailureHandler?(error!.localizedDescription)
}
}
dataTask.resume()
}
//============================================
//MARK: - Alert / Activity Indicator
//============================================
func showIndicator() {
SVProgressHUD.show()
}
func showMessageIndicator(message: String) {
hideIndicator()
SVProgressHUD.show(withStatus: message)
}
func hideIndicator() {
SVProgressHUD.dismiss()
}
func showErrorAlert(error: String){
hideIndicator()
SVProgressHUD.showError(withStatus: error)
}
func showWithStatus(status: String){
hideIndicator()
SVProgressHUD.showInfo(withStatus: status)
}
}
And put this code in your button action
func InApp()
{
SVProgressHUD.show()
if InAppPurchaseManager.sharedManager.canPurchase(){
InAppPurchaseManager.sharedManager.purchaseProductWithProductId(productId: PRODUCT_ID, fetchHandler: { (productInfoMessage, price) in
SVProgressHUD.dismiss()
InAppPurchaseManager.sharedManager.buyProduct()
print(productInfoMessage)
}, successHandler: { (transaction) in
self.purchaseSuccessful(transaction)
transectionID = transaction.transactionIdentifier!
}, failureHandler: { (error) in
print(error)
InAppPurchaseManager.sharedManager.hideIndicator()
)
InAppPurchaseManager.sharedManager.hideIndicator()
})
}else{
print( "Device is not able to make purchase. \n Please enable it from Settings -> General -> Restrictions -> In-App Purchases")
}
}
func purchaseSuccessful(_ transaction : SKPaymentTransaction) {
do {
if let receiptURL = Bundle.main.appStoreReceiptURL {
let data = try Data(contentsOf: receiptURL)
if data != nil {
InAppPurchaseManager.sharedManager.verifyPaymentReceipt(base64EncodedString: data.base64EncodedString(),
ReceiptValidationPassword: inAppPassword,
receiptSuccess: { (response) in
print("response \(response)")
if let receipt = response as? NSDictionary {
if let receiptInfo = receipt.object(forKey: "receipt" as NSCopying) as? NSDictionary{
let strOriginalPurchaseTime = receiptInfo.object(forKey: "receipt_creation_date_ms" as NSCopying) as! String
let timeInMS = TimeInterval(Double(strOriginalPurchaseTime)!)
let date = Date(timeIntervalSince1970: timeInMS / 1000)
purchaseDate = "\(date)"
print("date of purchase \(date)")
}
}
}, receiptFailure: { (error) in
print("error \(error)")
})
}
}
}catch {
}
}

Swift Core Data Class method?

I'm currently learning Core Data and I have two view controllers that are using the same piece of code to get a users profile. The problem is that it's the same code copy and pasted and I would like to avoid this. I'm using the Managed Class approach to access the data and each controller has the following method:
var profileHolder: Profile!
let profileRequest = Profile.createFetchRequest()
profileRequest.predicate = NSPredicate(format: "id == %d", 1)
profileRequest.fetchLimit = 1
if let profiles = try? context.fetch(profileRequest) {
if profiles.count > 0 {
profileHolder = profiles[0]
}
}
if profileHolder == nil {
let newProfile = Profile(context: context)
newProfile.id = 1
newProfile.attempts = nil
profileHolder = newProfile
}
profile = profileHolder
Profile is a var inside the controller: var profile: Profile! and I call the above inside viewWillAppear()
I know there's a cleaner approach and I would like to move this logic inside the class but unsure how to.
Thanks
var profileHolder: Profile!
profileHolder here is force unwrapping optional value. And you are fetching from core data and assigning the value in viewWillAppear, which is risky as profileHolder would be nil and can trigger crash if you access it before viewWillAppear.
My suggestion would be:
var profileHolder: Profile
{
if let profiles = try? context.fetch(profileRequest),
profiles.count > 0
{
return profiles[0]
}
else
{
let newProfile = Profile(context: context)
newProfile.id = 1
newProfile.attempts = nil
return newProfile
}
}()
This will ensure profileHolder is either fetched or created when the view controller is initialised.
However this would not work if
context
is a stored property of viewController, in which case, do:
var profileHolder: Profile?
override func viewDidLoad()
{
if let profiles = try? context.fetch(profileRequest),
profiles.count > 0
{
return profiles[0]
}
else
{
let newProfile = Profile(context: context)
newProfile.id = 1
newProfile.attempts = nil
return newProfile
}
}
Here is the struct I created for a project I did that allows me to access my CoreData functions anywhere. Create a new empty swift file and do something like this.
import CoreData
// MARK: - CoreDataStack
struct CoreDataStack {
// MARK: Properties
private let model: NSManagedObjectModel
internal let coordinator: NSPersistentStoreCoordinator
private let modelURL: URL
internal let dbURL: URL
let context: NSManagedObjectContext
let privateContext: NSManagedObjectContext
// MARK: Initializers
init?(modelName: String) {
// Assumes the model is in the main bundle
guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "momd") else {
print("Unable to find \(modelName)in the main bundle")
return nil
}
self.modelURL = modelURL
// Try to create the model from the URL
guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
print("unable to create a model from \(modelURL)")
return nil
}
self.model = model
// Create the store coordinator
coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
// create a context and add connect it to the coordinator
//context.persistentStoreCoordinator = coordinator
privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = coordinator
context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = privateContext
// Add a SQLite store located in the documents folder
let fm = FileManager.default
guard let docUrl = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
print("Unable to reach the documents folder")
return nil
}
self.dbURL = docUrl.appendingPathComponent("model.sqlite")
// Options for migration
let options = [NSInferMappingModelAutomaticallyOption: true,NSMigratePersistentStoresAutomaticallyOption: true]
do {
try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: options as [NSObject : AnyObject]?)
} catch {
print("unable to add store at \(dbURL)")
}
}
// MARK: Utils
func addStoreCoordinator(_ storeType: String, configuration: String?, storeURL: URL, options : [NSObject:AnyObject]?) throws {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: dbURL, options: nil)
}
}
// MARK: - CoreDataStack (Removing Data)
internal extension CoreDataStack {
func dropAllData() throws {
// delete all the objects in the db. This won't delete the files, it will
// just leave empty tables.
try coordinator.destroyPersistentStore(at: dbURL, ofType:NSSQLiteStoreType , options: nil)
try addStoreCoordinator(NSSQLiteStoreType, configuration: nil, storeURL: dbURL, options: nil)
}
}
// MARK: - CoreDataStack (Save Data)
extension CoreDataStack {
func saveContext() throws {
/*if context.hasChanges {
try context.save()
}*/
if privateContext.hasChanges {
try privateContext.save()
}
}
func autoSave(_ delayInSeconds : Int) {
if delayInSeconds > 0 {
do {
try saveContext()
print("Autosaving")
} catch {
print("Error while autosaving")
}
let delayInNanoSeconds = UInt64(delayInSeconds) * NSEC_PER_SEC
let time = DispatchTime.now() + Double(Int64(delayInNanoSeconds)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: time) {
self.autoSave(delayInSeconds)
}
}
}
}
Create a class(CoreDataManager) that can manage core data operations.
import CoreData
class CoreDataManager:NSObject{
/// Application Document directory
lazy var applicationDocumentsDirectory: URL = {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1]
}()
/// Core data manager
static var shared = CoreDataManager()
/// Managed Object Model
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = Bundle.main.url(forResource: “your DB name”, withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
/// Persistent Store Coordinator
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
let options = [ NSInferMappingModelAutomaticallyOption : true,
NSMigratePersistentStoresAutomaticallyOption : true]
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: options)
persistanceStoreKeeper.sharedInstance.persistanceStorePath = url
} catch {
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
abort()
}
return coordinator
}()
/// Managed Object Context
lazy var managedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
/// Save context
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
Add the bellow function in your class.
func fetchProfile(profileId:String,fetchlimit:Int,completion: ((_ fetchedList:["Your model class"]) -> Void)){
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Your entity name")
let predicate:NSPredicate = NSPredicate(format: "id = %#", profileId)
fetchRequest.predicate=predicate
fetchRequest.fetchLimit = fetchlimit
do {
let results =
try CoreDataManager.shared.managedObjectContext.fetch(fetchRequest)
let profileList:["Your model class"] = results as! ["Your model class"]
if(profileList.count == 0){
//Empty fetch list
}
else{
completion(profileList)
}
}
catch{
//error
}
}
replace "Your model class" according to your requirement.
You can call the function "fetchProfile" and you will get the result inside the completion block.

Swift: Ensure urlSession.dataTask is completed in my function before passing result

Hello I have this function:
func planAdded(id:Int, user_id:Int) -> Int {
let locationURL = "myurl"
var planResult: Int = 0
let request = URLRequest(url: URL(string: locationURL)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request, completionHandler:{
(data, response, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print (error)
return
}
if let data = data {
let responseString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
planResult = responseString!.integerValue
}
}
})
task.resume()
print(planResult)
return planResult
}
What I am trying to do is to ensure that I got the result for planResult in tableView cellforrow at indexpath function.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
case 4:
if (result == 1){
...
} else if (result == 2){
...
} else {
...
}
default:
cell.fieldLabel.text = ""
}
return cell
}
Here is my viewDidLoad function
override func viewDidLoad() {
super.viewDidLoad()
self.result = self.planAdded(1, 2)
}
For some reasons, this keeps returning 0; however, the print line is actually printing correct value. I did some research and I believe this is because of asychonous call of the dataTask. Is there a way I ensure that my function is actually completed and return the value for the indexpath function?
Thanks
The reason is, you are doing it in a wrong way! Because, once you intialize the class the UIViewController lifecycle starts. Once the viewDidLoad() is called it the UITableView is also updated with no data.
Also, you are calling API to get the data, you need to notify UITableViewDataSource to update data and here is how you can do that!
func planAdded(id:Int, user_id:Int) {
let locationURL = "myurl"
var planResult: Int = 0
let request = URLRequest(url: URL(string: locationURL)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request, completionHandler:{
(data, response, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print (error)
return
}
if let data = data {
let responseString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
self.result = responseString!.integerValue
self.tableView.reloadData()
}
}
})
task.resume()
}
And you are getting zero value because it's an async method. So get the data you need to use completionCallback.
func planAdded(id:Int, user_id:Int, completion: (result: Int) -> ()) {
let locationURL = "myurl"
var planResult: Int = 0
let request = URLRequest(url: URL(string: locationURL)!)
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request, completionHandler:{
(data, response, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print (error)
return
}
if let data = data {
let responseString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
planResult = responseString!.integerValue
completion(planResult)
}
}
})
task.resume()
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
planAdded(1, 2){(value) in
self.result = value
self.tableView.reloadData()
}
}

Pull to refresh returns empty array in Swift 3 iOS 10

I have recently migrated my iOS app to support iOS 10 with Swift 2.3 and have encountered an issue which was previously working fine on iOS 9 / Swift.
Problem goes like this:
I have a UITableView which loads up sections and rows, at the first instance everything loads fine. But when I do a pull to refresh i am clearing out the array by totalSectionData.removeAllObjects(). now when the refresh is executed it is throwing an error [__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array. It is obvious I am calling the function to reload the data from server after clearing out the array.
On further investigating and putting breakpoints I found that it is not going inside numberOfSectionsInTableView and numberOfRowsInSection while I do refresh; On the first time it does goes inside it. tableView and delegate are connected properly.
If i comment totalSectionData.removeAllObjects() then it works fine but I get duplicate rows.
It was working fine on previous iOS 9 / Swift 2, I tested the code again on previous version to double confirm.
Is there anything that's new in iOS 10 / Swift 2.3 that I am missing / doing wrong way.
Refresh code:
func refresh(sender:AnyObject)
{
// Code to refresh table view
tableView.allowsSelection = false
totalSectionData.removeAllObjects()
get_data_from_url(json_data_url)
}
After the JSON data is received I am calling reloadData
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
self.refreshControl.endRefreshing()
self.hideActivityIndicator()
self.tableView.allowsSelection = true
return
})
}
// Fetch JSON
func get_data_from_url(url:String)
{
let url:NSURL = NSURL(string: url)!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
tableView.allowsSelection = false
let task = session.dataTaskWithRequest(request) {
(
data, response, error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
self.showActivityIndicator()
dispatch_async(dispatch_get_main_queue(), {
let json = NSString(data: data!, encoding: NSASCIIStringEncoding)
self.extract_json(json!)
// self.activityIndicator.stopAnimating()
return
})
}
task.resume()
}
func extract_json(data:NSString)
{
var parseError: NSError?
let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
let json: AnyObject?
do {
json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
} catch let error as NSError {
parseError = error
json = nil
}
if (parseError == nil)
{
if let list:NSArray = json as? NSArray
{
print ("Inside if let")
let lblArray:NSArray = (list.valueForKey("level_number") as?NSArray)!
let data = NSOrderedSet(array: lblArray as [AnyObject])
for value in data {
let resultPredicate : NSPredicate = NSPredicate(format: "level_number == %d", value.integerValue)
let arrayData:NSArray = list.filteredArrayUsingPredicate(resultPredicate)
let lblArray:NSArray = (arrayData.valueForKey("part_number") as?NSArray)!
let dataValue = NSOrderedSet(array: lblArray as [AnyObject])
for index in dataValue {
let resultPredicate : NSPredicate = NSPredicate(format: "part_number == %d", index.integerValue)
totalSectionData.addObject(arrayData.filteredArrayUsingPredicate(resultPredicate))
}
}
for i in 0 ..< list.count
{
if let data_block = list[i] as? NSDictionary
{
TableData.append(datastruct(add: data_block))
}
}
do
{
try read()
}
catch
{
}
do_table_refresh()
}
}
}

How to make multiple api web server calls and have an activity indicator active inside a tableViewCell while 2nd api is being called?

Currently I am calling two web service apis, one for data and one for video. If I call the video api after the data one, the data cells populate and after the video api finishes connecting the video cell "pops out" because there was no cell for it before it completed loading. I tried reloading the tableview only after the video api had finished making its call but the loading time increased dramatically. Is there a way to have all the data from the the first api available and have an activity indicator running inside the video cell while the video api is being called? Here is my code, its been abbreviated to save space:
Get the data json:
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let urlConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: urlConfig)
task = session.dataTaskWithURL(url) {(data, response, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.updateDetailShowInfo(data!)
spinner.hide()
let videoURL = ""
self.getVideoJSON(videoURL)
} else {
spinner.hide()
self.showNetworkError()
}
}
}
task!.resume()
}
get the video json:
func getVideoJSON (urlString: String) {
let url = NSURL(string: urlString)!
let urlConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: urlConfig)
task = session.dataTaskWithURL(url) {(data, response, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.updateVideo(data!)
spinner.hide()
} else {
spinner.hide()
self.showNetworkError()
}
}
}
task!.resume()
}
update data (this was really long so I only showed the data being added to local arrays after they were gathered):
func updateDetailShowInfo (data: NSData!) {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
let banner = jsonResult["banner"] as? String ?? ""
detailsArray.append(showInfo)
bannerArray.append(banner)
overViewArray.append(overview)
totalResultsArray.append(detailsArray)
totalResultsArray.append(bannerArray)
totalResultsArray.append(overViewArray)
totalResultsArray.append(castLocalArray)
photosArray.append(poster)
photosArray.append(fanart)
photosArray.append(artwork)
totalResultsArray.append(photosArray)
exploreArray.append(exploreInfo)
totalResultsArray.append(exploreArray)
}
catch {
showNetworkError()
}
DetailTvTableView.reloadData()
}
update video data (only showing added to array):
func updateVideo (data: NSData!) {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
videoURL.append(videoView!)
totalResultsArray.append(videoURL)
}
}
}
}
catch {
showNetworkError()
}
self.DetailTvTableView.reloadData()
}
tableView:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return totalResultsArray.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: return bannerArray.count
case 1: return videoURL.count
case 2: return overViewArray.count
case 3: return min(1, castLocalArray.count)
case 4: return detailsArray.count
case 5: return socialArray.count
case 6: return exploreArray.count
case 7: return min(1, photosArray.count)
default: fatalError("Unknown Selection")
}
}
video cellForRowAtIndexPath in tableView:
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("videoDetail", forIndexPath: indexPath) as!
VideoOnDetailCell
cell.videoDetailWebView.delegate = cell
cell.videoDetailWebView.allowsInlineMediaPlayback = true
self.DetailTvTableView.rowHeight = 200
cell.videoDetailWebView.loadHTMLString("<iframe width=\"\(cell.videoDetailWebView.frame.width)\" height=\"\(200)\" src=\"\(videoURL[indexPath.row])/?&playsinline=1\" frameborder=\"0\" allowfullscreen></iframe>", baseURL: nil)
}
video cell:
class VideoOnDetailCell: UITableViewCell, UIWebViewDelegate {
#IBOutlet weak var videoDetailWebView: UIWebView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
func webViewDidStartLoad(webView: UIWebView) {
activityIndicator.hidden = false
activityIndicator.startAnimating()
}
func webViewDidFinishLoad(webView: UIWebView){
activityIndicator.hidden = true
activityIndicator.stopAnimating()
}
}