Class as a delegate placeholder for a struct - swift

I have a simple struct where I use NSURLSession to get the data from the web. I would like to extend it to provide progress of downloading. I cannot use struct as a NSURLSession delegate because it needs to be a NSObject so I have created simple class where I pass to NSURLSession delegate. This is short version of my code:
struct Resource {
private var progressDelegate: ProgressSessionDelegate?
private var session: NSURLSession!
init() {
self.progressDelegate = ProgressSessionDelegate()
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self.progressDelegate!, delegateQueue: nil)
}
func loadAsynchronous(callback: A? -> ()) {
session.dataTaskWithURL(resourceURL) {
data, response, error in
let json = data.flatMap {
try? NSJSONSerialization.JSONObjectWithData($0, options: NSJSONReadingOptions())
}
callback(mycalback)
}.resume()
}
}
and thats my cass which should handle the delegate methods:
class ProgressSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
var expectedContentLength = 0
var currentLength = 0
override init() {
super.init()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
print("0: \(expectedContentLength)")
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.currentLength += data.length
let percentageDownloaded = Float(self.currentLength) / Float(self.expectedContentLength)
print("1: \(percentageDownloaded)")
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
print("Downloaded")
}
}
The problem is that the delegate methods are not called.
When I move the code from Resource and ProgressSessionDelegate struct to my ViewController and set up ViewController as a NSURLSession delegate all is working fine. I think the problem is in a way how the class is stored is struct.
Any help would be most appreciated.

Related

How to add sessionDelegate option on SocketManager's config Swift

I'm tryng to connect to a self signed SSL URL.
But when I add sessionDelegate as option it's not working.
import Foundation
import SocketIO
import UIKit
class SocketM: NSObject, URLSessionDelegate {
static var manager = SocketManager(socketURL: URL(string:"https://localhost:8000")!, config: [.log(true), .secure(true), .selfSigned(true), .sessionDelegate(self)])
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let protectionSpace = challenge.protectionSpace
guard protectionSpace.authenticationMethod ==
NSURLAuthenticationMethodServerTrust,
protectionSpace.host.contains(Services_Routes.host) else {
completionHandler(.performDefaultHandling, nil)
return
}
guard let serverTrust = protectionSpace.serverTrust else {
completionHandler(.performDefaultHandling, nil)
return
}
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
}
It returns me
Type 'Any' has no member 'sessionDelegate'
When I try :
SocketIOClientOption.sessionDelegate(self)
Type '(SocketM) -> () -> SocketM' does not conform to protocol 'URLSessionDelegate'
Can someone explain me the problem?
Thanks !
You are creating static variable and passing delegate as "self", you can't use self before initialising object.
If you don't need static object of manager then you can write code as
class SocketM: NSObject, URLSessionDelegate {
var manager: SocketManager?
override init() {
super.init()
manager = SocketManager(socketURL: URL(string:"https://localhost:8000")!, config: [.log(true), .reconnects(true), .selfSigned(true), .sessionDelegate(self)])
}
}
And If you want static manager the
class SocketM: NSObject, URLSessionDelegate {
static var manager = SocketManager(socketURL: URL(string:"https://localhost:8000")!, config: [.log(true), .reconnects(true), .selfSigned(true)])
override init() {
super.init()
SocketM.manager.config.insert(.sessionDelegate(self))
}
}

How to know when a PDF is rendered using PDFKit

I'm trying to show a loading while a PDF ins't already been shown on the screen. The problem is, my loading always stops before the document is already rendered, sometimes it can't take 2 or 3 seconds and I need to know when the PDF is already rendered to stop the activyIndicator. IS IT possible using PDFKIT? My code:
class PDFViewController: URLSessionDownloadDelegate {
#IBOutlet weak var pdfView: PDFView!
var pdfDocument: PDFDocument!
override func viewDidLoad() {
super.viewDidLoad()
setupPDFView()
setupNavigationBar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.startLoading()
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadPDF()
}
private func loadPDF() {
DispatchQueue.main.async {
guard let url = URL(string: self.viewModel.pdfURL) else {
self.showAlert(with: self.viewModel.strings.invalidInvoice)
return
}
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
let downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
}
}
private func setupPDFView() {
self.pdfView.displayMode = .singlePageContinuous
self.pdfView.autoScales = true
}
func startLoading() {
guard let window = UIApplication.shared.keyWindow,
!window.subviews.contains(where: { $0 is LoadingView }) else { return }
let loadingView = LoadingView(frame: window.bounds)
window.addSubview(loadingView)
Thread.performUIUpdate {
loadingView.startAnimation()
}
}
func stopLoading() {
guard let window = UIApplication.shared.keyWindow,
let view = window.subviews.first(where: { $0 is LoadingView }),
let loadingView = view as? LoadingView else { return }
Thread.performUIUpdate {
loadingView.stopAnimation()
loadingView.removeFromSuperview()
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
self.pdfDocument = PDFDocument(url: location)
if let pdfDocument = self.pdfDocument {
self.pdfView.document = pdfDocument
}
self?.stopLoading()
}
}
}
This seems like a really bad idea:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
self.pdfDocument = PDFDocument(url: location)
The problem is that while you are getting off the current thread and the asynchronous code starts, the method finishes and the temporary document at location can be destroyed. I would suggest writing like this:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
let pdf = PDFDocument(url: location)
DispatchQueue.main.async {
self.pdfDocument = pdf
I'm not saying that that will solve your issue, but it seems a lot less dangerous than what you're doing.
As for your actual issue, I would suggest registering for a notification such as this one and see whether it arrives at the right moment.
You have to observe the PDFViewVisiblePagesChanged notification to know when a PDFView pages are visible, add your code in the method.
NotificationCenter.default.addObserver(self, selector: #selector(pdfViewVisiblePagesChanged(sender:)), name: .PDFViewVisiblePagesChanged, object: nil)
#objc func pdfViewVisiblePagesChanged(sender: Notification) {
//Add your code here
}

Getting data out of a struct for use in a table view

I'm unable to get a struct containing vars loaded from JSON into a format suitable for a table view
I've been playing around with this code for a while, done plenty of reading but I'm just plain stuck. I can get data from the server fine and print it to the console. I notice that the data is printed as KEY: "value" rather than "KEY":"value" and that might have something to do with it. Also as the code is right now I can count the number of rows correctly and display the value of FIRST in the table view. What I can't figure out is how to access the other variables for display. Any help is greatly appreciated!
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
static var globalPatientInstance: [Patient] = []
var gotJSON: ([Patient], Error?) -> Void = { (patients, error) in
print("patients from gotJSON: \(patients)")
globalPatientInstance = patients
// this returns an error: "Type 'Patient' does not conform to protocol 'Sequence'"
/*
for (key, value) in patients[0] {
println("\(key) -> \(value)")
tableArray.append(Objects(sectionName: key, sectionObjects: value))
}
*/
print("patients from gotJSON: \(globalPatientInstance[0])")
}
#IBOutlet var tableView: NSTableView!
#IBAction func button(_ sender: Any) {
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.delegate = self as NSTableViewDelegate
self.tableView.dataSource = self
let url = URL(string: "http://172.16.1.25/backend/returnA")
let returnA = URLRequest(url: url!)
retrieveJSON(with: returnA, completionHandler: gotJSON)
self.tableView.reloadData()
}
func retrieveJSON(with request: URLRequest, completionHandler: #escaping ([Patient], Error?) -> Void) {
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: request as URLRequest) {
// completion handler argument
(data, response, error) in
// completion handler
guard let data = data else {
print("Did not recieve data")
completionHandler([], error)
return
}
do {
let decoder = JSONDecoder()
let patients = try decoder.decode(Array<Patient>.self, from: data)
// print(Patient)
completionHandler(patients, error)
}
catch let err {
print("Err", err)
completionHandler([], error)
}
}
task.resume()
}
func numberOfRows(in tableView: NSTableView) -> Int {
return ViewController.globalPatientInstance.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var result: NSTableCellView
result = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as! NSTableCellView
result.textField?.stringValue = ViewController.globalPatientInstance[row].FIRST
return result
}
}
This code runs fine. The array contains three structs and the value of FIRST is successfully displayed in the table view when the button is pressed.

URLSessionDataDelegate methods not being called

I'm starting a download session in a view controller:
class MyController: UIViewController {
func startDownload(withURL url: URL) {
downloadSession = URLSession(configuration: URLSessionConfiguration.default,
delegate: self,
delegateQueue: OperationQueue.main)
downloadTask = downloadSession.dataTask(with: url)
downloadTask.resume()
}
}
The delegate methods are defined in an extension:
extension MyController: URLSessionDelegate {
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data) {
...
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
...
}
}
Why are these delegate methods never called?
You only declare conformance to URLSessionDelegate in your extension. If you declare conformance to URLSessionDataDelegate instead, your delegate methods will be called.

URLSessionDelegate's didWriteData not call when app is going to background in iOS12

I want to implement downloading functionality which can show completed status of downloading task with the percentage. And I'm able to do that but the problem is when the app is moving to the background and come back to the foreground at that time the delegate method didWriteData is not called in iOS12. Can anyone please help me? Here is my code
protocol DownloadDelagate {
func downloadingProgress(value:Float)
func downloadCompleted(identifier: Int,url: URL)
}
class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
static var shared = DownloadManager()
var delegate: DownloadDelagate?
var backgroundSessionCompletionHandler: (() -> Void)?
var session : URLSession {
get {
let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
}
}
private override init() {
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let completionHandler = self.backgroundSessionCompletionHandler {
self.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
delegate?.downloadCompleted(identifier: downloadTask.taskIdentifier, url: location)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
let progressPercentage = progress * 100
delegate?.downloadingProgress(value: progressPercentage)
print("Download with task identifier: \(downloadTask.taskIdentifier) is \(progressPercentage)% complete...")
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("Task failed with error: \(error)")
} else {
print("Task completed successfully.")
}
}
}
Based on this thread this is a bug in NSURLSesstion. Currently there are known workaround for this (approved by Apple Engineers):
var session: URLSession?
...
func applicationDidBecomeActive(_ application: UIApplication) {
session?.getAllTasks { tasks in
tasks.first?.resume() // It is enough to call resume() on only one task
// If it didn't work, you can try to resume all
// tasks.forEach { $0.resume() }
}
}
Please try your code in AppDelegate's applicationWillEnterForeground(). You can make changes here when the app makes transition from Background to Active state.