SwiftUI - Publish Background Thread Not Allowed - on code that does not update ui - swiftui

New to swiftui and don't understand why the JSONDecoder() line in the first code throws
[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
This to me is not updating ui so why is this showing?
do {
// pass the request type, bearer token, and email / password ( which are sent as POST body params )
let request = L.getRequest(requestType:"POST", token: token, email: self.username, password: self.psw)
L.fetchData(from: request) { result in
switch result {
case .success(let data):
// covert the binary data to a swift dictionary
do {
let response = try JSONDecoder().decode(WpJson.self, from: data)
for (key, title) in response.allowedReadings {
let vimeoId = Int( key )!
let vimeoUri = self.buildVimeoUri(vimeoId: key)
self.addReadingEntity(vimeoUri: vimeoUri, vimeoId: vimeoId, title: title)
}
self.writeToKeychain(jwt:response.jwt, displayName: response.displayName)
readings = self.fetchReadings()
}
catch {
self.error = error.localizedDescription
}
case .failure(let error):
self.error = error.localizedDescription
}
}
}
I tried wrapping a main queue around the do-catch in the L.fetchData(from: request) { result in but this did not help
DispatchQueue.main.async { [weak self] in
Here is the Login protocol, again without any ui work:
import Foundation
import SwiftUI
struct Login: Endpoint {
var url: URL?
init(url: URL?) {
self.url = url
}
}
protocol Endpoint {
var url: URL? { get set }
init(url: URL?)
}
extension Endpoint {
func getRequestUrl() -> URLRequest {
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object
return URLRequest(url: requestUrl)
}
func getRequest(requestType:String="POST", token:String, email:String="", password:String="") -> URLRequest {
var request = self.getRequestUrl()
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if ( "" != email && "" != password && requestType == "POST") {
let parameters:[String:String?] = [
"email": email,
"password": password
]
// Run the request
do {
// pass dictionary to nsdata object and set it as request body
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
}
return request;
}
func fetchData(from request: URLRequest, completion: #escaping (Result<Data, NetworkError>) -> Void) {
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
completion(.success(data))
} else if error != nil {
// any sort of network failure
completion(.failure(.requestFailed))
} else {
// this ought not to be possible, yet here we are
completion(.failure(.unknown))
}
}.resume()
}
}
extension URLSession {
func dataTask(with request: URLRequest, completionHandler: #escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> URLSessionDataTask {
return dataTask(with: request, completionHandler: { (data, urlResponse, error) in
if let error = error {
completionHandler(.failure(error))
} else if let data = data, let urlResponse = urlResponse as? HTTPURLResponse {
completionHandler(.success((data, urlResponse)))
}
})
}
}
Do you have any idea on how to fix this?

Wrap it right in place of assignment
catch {
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}
case .failure(let error):
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}

Related

How to set userSession in AuthView from SignInWithAppleButton in LoginView in SwiftUI

LoginView
SignInWithAppleButton(
onRequest: { request in
let nonce = randomNonceString()
currentNonce = nonce
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
},
onCompletion: { result in
switch result {
case .success(let authResults):
switch authResults.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
let credential = OAuthProvider.credential(withProviderID: "apple.com",idToken: idTokenString, rawNonce: nonce)
Auth.auth().signIn(with: credential) { (authResult, error) in
if (error != nil) {
print(error?.localizedDescription as Any)
return
}
guard let user = authResult?.user else {return}
viewModel.userSession = user
I cannot put viewModel.userSession = user either without compile-time error and on some lucky occasions, on app crashing. So the app crashes on apple login and on opening the app, everything works fine.
AuthViewModel
#Published var userSession: FirebaseAuth.User?
#Published var currentUser: User?
Normal Login -->
func login(withEmail email: String, password: String) {
Auth.auth().signIn(withEmail: email, password: password) {result, error in
if let error = error {
self.hapticFeedback.notificationOccurred(.error)
self.errorMessage = "\(error.localizedDescription)"
self.errorOccurred.toggle()
print("DEBUG: Failed to Signin with error \(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
self.userSession = user
self.fetchUser()
self.writeUserData()
}

Json parsing in Swift 3.0

This is my code for Jason parsing in Swift:
static func POST(url: String, parameters: NSDictionary, completionBlock: #escaping CompletionBlock){
let todoEndpoint: String = Webservices.Base_Url.appending(url)
guard let url = NSURL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
var request = URLRequest(url: url as URL)
//var request = URLRequest(url: NSURL(string: todosEndpoint)! as URL)
let session = URLSession.shared
request.httpMethod = "POST"
var err: NSError?
let jsonData = try? JSONSerialization.data(withJSONObject: parameters)
request.httpBody = jsonData
request.addValue("application/x-www-form-urlencoded;charset=UTF-8 ", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
guard error == nil else {
print("error calling POST on /todos/1")
print(error)
return
}
// make sure we got data
guard let dataTemp = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try JSONSerialization.jsonObject(with: dataTemp, options: []) as? [String: AnyObject] else {
print("error trying to convert data to JSON")
return
}
// now we have the todo, let's just print it to prove we can access it
print("The todo is: " , todo)
// the todo object is a dictionary
// so we just access the title using the "title" key
// so check for a title and print it if we have one
} catch {
print("error trying to convert data to JSON")
return
}
})
task.resume()
}
I got while jason parsing:
error expression produced error: error: Execution was interrupted,
reason: EXC_BAD_ACCESS (code=1, address=0x0). The process has been
returned to the state before expression evaluation.
What's wrong?

How do I make task wait for completion in Swift 3

I'm using Alamofire to send a login request to an HTTP server. It returns a JSON response. My problem is that the mainline code finishes before the .responseJSON. How do I wait until the response is returned before returning from the function?
var ret: Bool = true
Alamofire.request(
URL(string: "http://localhost:8081/login/iPhone")!,
method: .post,
parameters: ["email":"test#test.test", "password":"test", "uuid":String(describing: UIDevice.current.identifierForVendor!.uuidString)],
headers: [:])
.validate()
.responseJSON{(response) -> Void in
do {
guard response.result.isSuccess else {
throw FieldError.fetchError(responseError: response.result.error)
}
guard let value = response.result.value as? [String: Any],
let status = value["status"] as? String,
let message = value["message"] as? String else {
throw FieldError.messageFormatError
}
switch status {
case "Login suggess":
break
default:
throw FieldError.fieldServerError(status: status, message: message)
}
} catch {
ret = false
debugPrint(error)
}
}
return ret
You can always use global
typealias DownloadComplete = () -> ()
next in func with you're json
func yourFunc(completed: #escaping DownloadComplete)
and after download use
completed()

Swift 3: Alamorefire block UI

I have APIManager singleton class and have a function to get data from server like this:
func scanOrder(order: String, completion:#escaping Handler){
let url = K.API_URL + "/api/containers/picking/" + order
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: getHeader()).responseJSON { (response) in
DispatchQueue.main.async {
completion(response)
}
}
}
and I call this function in other class like this:
apiMan.scanOrder(order: tfCode.text!) { (response) in
...
}
while waiting for server to response, my UI is blocked. I tried to wrap alamofire request call within DispatchQueue.global().async but it still blocks the UI.
Please help!
I never used Alamofire.request with DispatchQueue.main.async like you do. The reason is that Alamofire in combination with completion blocks already operates async and shouldn't block the UI, which is settled in the Main Thread.
Have you tried something like:
class NetworkManager {
func scanOrder(order: String, completion:#escaping (Any?) -> Void){
let url = "https://example.com/api/containers/picking/" + order
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: AppConfiguration.sharedInstance.defaultHeader())
.responseJSON { response in
guard response.result.isSuccess else {
Log.info("Error while fetching: \(response.result.error)")
completion(nil)
return
}
guard let responseJSON = response.result.value as? [String: AnyObject] else {
Log.info("Invalid information received from service")
completion(nil)
return
}
completion(responseJSON)
}
}
}
Call:
class CallingClass {
func scanOrder(order:String){
let manager = NetworkManager()
var result: Any?
manager.scanOrder(order: "example") { response in
result = response
}
print(result as Any)
}
}

URLRequest gets HTTP error 502 and "connection reset by peer"

Got these errors with this code:
if let url = URL(string: "<valid web service url string>") {
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64Authorization)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request, completionHandler: {(data, response, error) in
if error == nil {
The same code returns no error with Xcode 7.3 but errors in Xcode 8 after converting to Swift 3.
This happened because of the Swift 3 proposal SE-0054.
base64Authorization was declared this way:
static var base64Authorization:String! {
get {
if base64Auth == nil {
let keyString = "<my key string>"
let plainTextData = keyString.data(using: .utf8, allowLossyConversion: false) as Data!
base64Auth = plainTextData!.base64EncodedString(options: .endLineWithLineFeed) as String!
}
return base64Auth
}
}
base64Authorization returned an Optional which messed up the "Basic \(base64Authorization)" HTTP setting.
This declaration of base64Authorization fixed the problem:
static var base64Authorization:String {
get {
if base64Auth == nil {
let keyString = "<my key string>"
let plainTextData = keyString.data(using: .utf8, allowLossyConversion: false) as Data!
base64Auth = plainTextData!.base64EncodedString(options: .endLineWithLineFeed) as String
}
return base64Auth!
}
}