Swift 3 and NSURLSession issue - swift3

Thanks to Apple my iOS 9 Project 'Swift 2.3' is completely unusable with iOS 10's 'Swift 3'...
I fixed almost everything except that I am having issue with using NSURLSession, Xcode is telling me that it has been renamed to URLSession, if I rename it Xcode will tell me:
use of undeclared type URLSession
Foundation is imported.
What is the issue?!
For example I am using it this way...
lazy var defaultSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "reCoded.BGDownload")
configuration.sessionSendsLaunchEvents = true
configuration.isDiscretionary = true
let session = URLSession(configuration: configuration, delegate: self, delegateQueue, queue: nil)
return session
}()
and even with the delegate methods the same issue.

Try using Foundation.URLSession where ever you use URLSession.

/Got it to work/ In some cases try to copy your code somewhere else then remove everything in your class that uses URLSession then type the session methods again and put back your copied code you should be fine.

Update your URLSessin functions with;
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.data.append(data as Data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}

I can explain how but by playing around with the code I got this to work in SWIFT 3 after two days of frustration. I guess SWIFT 3 removed a lot of unnecessary words.
let task = Foundation.URLSession.shared.dataTask(with: <#T##URL#>, completionHandler: <#T##(Data?, URLResponse?, Error?) -> Void#>)

Here's where I am right now. It's not perfect but works maybe half of the time.
First, in the class where my URLsession is defined:
import Foundation
class Central: NSObject, URLSessionDataDelegate, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate {
I don't think all of that is necessary, but there it is. Then here is the function that is called by my background fetch:
func getWebData() {
var defaults: UserDefaults = UserDefaults.standard
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myBGconfig")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
urlString = "https://www.powersmartpricing.org/psp/servlet?type=dayslider"
if let url = URL(string: urlString) {
let rateTask = backgroundSession.downloadTask(with: URL(string: urlString)!)
rateTask.taskDescription = "rate"
rateTask.resume()
}
When the task comes back:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) {
if downloadTask.taskDescription == "rate" { // I run 2 web tasks during the session
if let data = NSData(contentsOf: location) {
var return1 = String(data: data as! Data, encoding: String.Encoding.utf8)!
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 0.2){
var defaults: UserDefaults = UserDefaults.standard
defaults.set(myNumber, forKey: "electricRate") // myNumber is an extract of the text in returned web data
defaults.set(Date(), forKey: "rateUpdate")
defaults.synchronize()
self.calcSetting() //Calls another function defined in the same class. That function sends the user a notification.
let notificationName = Notification.Name("GotWebData")
NotificationCenter.default.post(name: notificationName, object: nil)
} // Closes the Dispatch
}
if session.configuration.identifier == "myBGconfig" {
print("about to invalidate the session")
session.invalidateAndCancel()
}
}
I haven't figured out yet how to kill the session when BOTH tasks have completed, so right now I kill it when either one is complete, with invalidateAndCancel as above.
And finally, to catch errors:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didCompleteWithError: Error?) {
if downloadTask.taskDescription == "rate" {
print("rate download failed with error \(didCompleteWithError)")
}
if downloadTask.taskDescription == "other" {
print("other download failed with error \(didCompleteWithError)")
}
downloadTask.resume() // I'm hoping this retries if a task fails?
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
if let error = error as? NSError {
print("invalidate, error %# / %d", error.domain, error.code)
} else {
print("invalidate, no error")
}
}

Related

URLSession HTTP Error not updating back in view unless the action is initiated again

I have a view with a button that calls an API, the API either returns an HTTP code 200 or 400 based on a particular scenario.
The button works just fine and everything works smoothly if code 200 is returned, however if code 400 is returned, the view is not updated that the user have to click on the button once again to get the updated message.
I added the http code property as a published variable in the VM's class and the http is an observable, but it doesn't get updated in the view on the first API call, I'm not sure what I'm missing.
I made a lot of changes to the shared code just to help in demonstrating the actual problem.
Update: Also I think another part of the problem, is that the url function returns the value before the url session returns the data, I don't know why this is happening, that when I execute it a second time it uses the values from the previous execution.
HTTPError Class
class HTTPError : Codable, ObservableObject {
var statusCode: Int?
var message: [String]?
var error: String?
init(statusCode: Int? = nil, message: [String]? = [], error: String? = nil){
self.statusCode = statusCode
self.message = message ?? []
self.error = error
}
convenience required init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.statusCode = try container.decodeIfPresent(Int.self, forKey: .statusCode)
do {
self.message = try container.decodeIfPresent([String].self, forKey: .message)
} catch {
guard let value = try container.decodeIfPresent(String.self, forKey:
.message) else {return}
self.message = []
self.message?.append(value)
}
self.error = try container.decodeIfPresent(String.self, forKey: .error)
}
VM Class
class VM: ObservableObject {
#Published var isLoading = true
#Published var httpError = HTTPError()
func checkDriverIn(_ record: DriverQRParam) async -> (Bool) {
...
var request = URLRequest(url: url)
...
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
...
let task = URLSession.shared.dataTask(with: request) { (data, response,
error) in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
self.httpError = try! JSONDecoder().decode(HTTPError.self, from: data)
//gets updated just fine in this class//
}
task.resume()
}catch {
print("Couldn't encode data \(String(describing: error))")
}
if httpError.statusCode != nil && httpError.statusCode == 400 {
return (false)
} else {
return (true)
}
}
View.Swift
struct xyz: View {
#State private var VM = VM()
Button("click") {
Task {
await VM.checkDriverIn(driverParam)
}
}
}

PHPicker result(s) incomplete

SwiftUI novice here.
My PHPicker results show a weird behaviour.
Whether I pick one image or several, often the result is empty for a single image or incomplete if multiple images are picked.
Oddities: every image that is missing from a PHPicker session result can be fetched in another session (so the image itself is okay), furthermore it happens that in the next session some images are additionally returned that had been selected in the session before but were missing.
There are no explicit error messages in the console, also the behaviour is completely unpredictable.
So let's say I pick 20 images in a session: 9 of them get returned and appended and maybe another 6 of them get returned additionally in the next session without being picked, so there are still 5 images missing which in turn are able to be picked in future sessions.
Further use of the PHPicker results works without problems; dates and paths are sent into Core Data and the images themselves saved to FileManager; this data is then combined in a list view.
I guess it might have to do with the interplay of the 3 parts (date, path, image) I fetch for each image, but I'm at a loss where exactly the problem arises.
struct PhotoPicker: UIViewControllerRepresentable {
#Binding var dates: [Date?]
#Binding var paths: [String?]
#Binding var images: [UIImage?]
#Environment(\.presentationMode) var presentationMode
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images
config.selectionLimit = 20
config.preferredAssetRepresentationMode = .current
let controller = PHPickerViewController(configuration: config)
controller.delegate = context.coordinator
return controller
}
func makeCoordinator() -> PhotoPicker.Coordinator {
return Coordinator(self)
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
class Coordinator: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.presentationMode.wrappedValue.dismiss()
guard !results.isEmpty else {
return
}
print(results)
for result in results {
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
if let assetId = result.assetIdentifier {
let assetResults = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil)
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) {
(url, error) in
if error != nil {
print("error \(error!)");
} else {
result.itemProvider.loadObject(ofClass: UIImage.self) {
(image, error) in
if error != nil {
print("error \(error!)");
} else {
if assetResults.firstObject?.creationDate != nil && url?.lastPathComponent != nil && image != nil {
Task { #MainActor in
self.parent.dates.append(assetResults.firstObject?.creationDate)
print(assetResults.firstObject?.creationDate as Any)
self.parent.paths.append(url?.lastPathComponent)
print(url?.lastPathComponent as Any)
self.parent.images.append(image as? UIImage)
print(image as Any)
}
}
}
}
}
}
}
}
}
}
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
}
}

SwiftUI combine nil data

I have created a class to perform a network request and parse the data using Combine. I'm not entirely certain the code is correct, but it's working as of now (still learning the basics of Swift and basic networking tasks). My Widget has the correct data and is works until the data becomes nil. Unsure how to check if the data from my first publisher in my SwiftUI View is nil, the data seems to be valid even when there's no games showing.
My SwiftUI View
struct SimpleEntry: TimelineEntry {
let date: Date
public var model: CombineData?
let configuration: ConfigurationIntent
}
struct Some_WidgetEntryView : View {
var entry: Provider.Entry
#Environment(\.widgetFamily) var widgetFamily
var body: some View {
VStack (spacing: 0){
if entry.model?.schedule?.dates.first?.games == nil {
Text("No games Scheduled")
} else {
Text("Game is scheduled")
}
}
}
}
Combine
import Foundation
import WidgetKit
import Combine
// MARK: - Combine Attempt
class CombineData {
var schedule: Schedule?
var live: Live?
private var cancellables = Set<AnyCancellable>()
func fetchSchedule(_ teamID: Int, _ completion: #escaping (Live) -> Void) {
let url = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule?teamId=\(teamID)")!
let publisher = URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Schedule.self, decoder: JSONDecoder())
//.catch { _ in Empty<Schedule, Error>() }
//.replaceError(with: Schedule(dates: []))
let publisher2 = publisher
.flatMap {
return self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
}
Publishers.Zip(publisher, publisher2)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in
}, receiveValue: { schedule, live in
self.schedule = schedule
self.live = live
completion(self.live!)
WidgetCenter.shared.reloadTimelines(ofKind: "NHL_Widget")
}).store(in: &cancellables)
}
func fetchLiveFeed(_ link: String) -> AnyPublisher<Live, Error /*Never if .catch error */> {
let url = URL(string: "https://statsapi.web.nhl.com\(link)")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Live.self, decoder: JSONDecoder())
//.catch { _ in Empty<Live, Never>() }
.eraseToAnyPublisher()
}
}
Like I said in the comments, it's likely that the decode(type: Live.self, decoder: JSONDecoder()) returns an error because the URL that you're fetching from when link is nil doesn't return anything that can be decoded as Live.self.
So you need to handle that case somehow. For example, you can handle this by making the Live variable an optional, and returning nil when link is empty (or nil).
This is just to set you in the right direction - you'll need to work out the exact code yourself.
let publisher2 = publisher1
.flatMap {
self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
.map { $0 as Live? } // convert to an optional
.replaceError(with: nil)
}
Then in the sink, handle the nil:
.sink(receiveCompletion: {_ in }, receiveValue:
{ schedule, live in
if let live = live {
// normal treatment
self.schedule = schedule
self.live = live
//.. etc
} else {
// set a placeholder
}
})
SwiftUI and WidgetKit work differently. I needed to fetch data in getTimeline for my IntentTimelineProvider then add a completion handler for my TimelineEntry. Heavily modified my Combine data model. All credit goes to #EmilioPelaez for pointing me in the right direction, answer here.

viewing response from URLSession

I have an endpoint which takes in a phone number and sends a code to the number, but also returns that same message to the data section of the session that called it.
All of that works, but the problem I'm having is that, after the session makes the call, I'm segueing to the next screen and i'm passing that code into the next controller. But i think the api is responding too slow, so by time the segue (and prep for segue) has happened the code has not been returned yet. How can i fix this?
let scriptURL = "https://---------------/api/verify/sms?"
let urlWithParams = scriptURL + "number=\(phone.text!)"
let myUrl = NSURL(string: urlWithParams)
let request = NSMutableURLRequest(url: myUrl! as URL)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
//print(error?.localizedDescription)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as AnyObject
self.currentCode = json["code"]!! as! String //-> This is the code the is returned from the api call
}catch{
print("error with serializing JSON: \(error)")
}
}
task.resume()
self.performSegue(withIdentifier: "toVerifyCode", sender: (Any?).self)
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "toVerifyCode"{
let newController = segue.destination as! verifyCodeController
newController.code = self.currentCode
}
}
The problem is that you placed self.performSegue(withIdentifier: "toVerifyCode", sender: (Any?).self) not in the closure.
So, you have to place it like this:
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
//print(error?.localizedDescription)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as AnyObject
//on main thread
DispatchQueue.main.async {
self.currentCode = json["code"]!! as! String //-> This is the code the is returned from the api call
self.performSegue(withIdentifier: "toVerifyCode", sender: (Any?).self)
}
}catch{
print("error with serializing JSON: \(error)")
}
}
task.resume()
Also, please note that your closure is executed asynchronously, so I wrapped the call to be executed on main thread by using GCD.

Resume Data from URLSessionDownloadTask Always nil

i have an app which i need to download the file from the internet when i downloading the file it's work good but my problem is when i pressed pause button to pause the downloading for one minute or more i get nil from resume Data
the following my code :
#IBAction func startDownloading(_ sender: UIButton)
{
isDownload = true
sessionConfig = URLSessionConfiguration.default
let operationQueue = OperationQueue.main
session = URLSession.init(configuration: sessionConfig, delegate: self, delegateQueue: operationQueue)
let url = URL(string: "www.example.com")
downloadTask = session.downloadTask(with: url!)
downloadTask.resume()
}
#IBAction func pause(_ sender: UIButton)
{
if downloadTask != nil && isDownload
{
self.downloadTask!.cancel(byProducingResumeData: { (resumeData) in
// here is the nil from the resume data
})
isDownload = false
downloadTask = nil
pasueBtnOutlet.setTitle("Resume", for: .normal)
}
if !isDownload && downloadData != nil
{
downloadTask = session.downloadTask(withResumeData: downloadData as Data)
downloadTask.resume()
isDownload = true
downloadData = nil
pasueBtnOutlet.setTitle("Pause", for: .normal)
}
}
please can help me
thanks for all
Your code seems to be correct, you just need to init the downloadData with resumeData in closure
Make a property of downloadData
var downloadData:Data!
and then in your pause button action where you cancel the task, set the downloadData with resumeData
self.downloadTask!.cancel(byProducingResumeData: { (resumeData) in
// here is the nil from the resume data
// You have to set download data with resume data
self.downloadData = resumeData
})
In order to check the progress and completion, implement these URLSessionDownloadDelegate delegates
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
print(Int(progress * 100))
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Downloading done")
}
NOTE:- Try a valid downloadable url. For example
http://www.star.uclan.ac.uk/news_and_events/news/2010020901/sdo_resolution_comparison.png
Its http, make sure to set Transport Security in Info.plist