How swiftui webview takeSnapshot works in background - swiftui

The following is my code, I want to save the web page image, save it, now the code can work, I want to ask, how to make it work in the background task, I tested it and put it in the background(BGTaskScheduler), but the image is a black screen, there is no content
let config = WKSnapshotConfiguration()
config.rect = model.webView.frame
config.afterScreenUpdates = false
model.webView.takeSnapshot(with: config, completionHandler: { (image: UIImage?, error: Error?) in
// UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.notebox.data")?.appendingPathComponent(boxId.uuidString)
do {
if let imageData = image!.jpegData(compressionQuality: 1) {
try imageData.write(to: url!)
}
} catch {
print("save error")
}
})

Related

SwiftUI - Publishing changes from background threads is not allowed

I am trying to create an app that creates random pictures when a button is clicked. The app is working fine but I see this message which I have never seen before."Publish changes from background threads is not allowed; make sure to publish values from the main thread.".
I am new to SwiftUI, help is appreciated.
import Foundation
import SwiftUI
class ImageviewModel{
var image: UIImage? = nil
//let url = URL(string: "https://source.unsplash.com/random/600x600")!
let url = URL(string: "https://picsum.photos/600/600")!
func responseHandler(data: Data?, response: URLResponse?) ->
UIImage?{
guard let data = data,
let image = UIImage(data: data),
let response = response else {return nil}
return image
}
func loadImageWithAsync() async throws -> UIImage?{
do{
let (data, response) = try await URLSession.shared.data(from: url,delegate: nil)
return responseHandler(data: data, response: response)
} catch{
throw error
}
}
}
class ViewModel: ObservableObject{
#Published var image: UIImage? = nil
var loader = ImageviewModel()
func fetchImage() async {
let image = try? await loader.loadImageWithAsync()
self.image = image
}
}
You need to add the MainActor wrapper to the class to guarantee that updates are done on Main
#MainActor
class ViewModel: ObservableObject{

Swift 3 submit form - UITextField changes only after focusing field again

I am working on a login view and trying to change the border color of a UITextField in Xcode/swift3 when validation of the textfield fails. The UITextField should get a red border color.
The problem is that if enter an email, then a password and then press the submit button, i have to focus email text field again before it gets a red border.
This is my LoginViewController.swift so far:
import Foundation
import UIKit
class LoginViewController : UIViewController, UITextFieldDelegate {
#IBOutlet weak var userEmailTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
// login button action
#IBAction func loginButtonTabbed(_ sender: Any) {
// getting values from text fields
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
// set enpoind data
let requestURL = NSURL(string: Constants.apiUrl)
//creating a task to send the post request
var request = URLRequest(url: requestURL as! URL)
request.httpMethod = "POST"
let postString = "cmd=addUser&email="+userEmail!+"&password="+userPassword!
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
do {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
// store json response to dictionary
if let dictionary = json as? [String: Any] {
// check if we got validation errors
if let nestedDictionary = dictionary["validation"] as? [String: Any] {
// display validation messages on device
if let emailMsg = nestedDictionary["Email"] as? String { // change color of textfield
self.userEmailTextField.errorField()
}
}
}
} catch let error as NSError {
print(error)
}
}
//executing the task
task.resume()
}
}
and the UITextField extension UITextField.swift:
import Foundation
import UIKit
extension UITextField {
func errorField(){
self.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
self.layer.borderWidth = 1.0;
}
}
When you're doing a network call, it always happens in the background...so in order to do any kind of UI updates you need to be on the main queue. Just put the self.userEmailTextField.errorField() inside DispatchQueue.main.async {...} so it would be done immediately.
Also haven't really tested your code very well. Why?
Even in your current code the border would still turn red, but it turns red after almost like 6-7 seconds (it could take less or more for you)...because it's being ran from background thread.
What I don't understand is why clicking on the textField again brings the red border right away!? Here's what I'm guessing happens:
From the background thread you update the model ie change the textField color which queues the UI/view to be updated...but since we're on a background queue, that UI updated could take a few seconds to happen
But then you tapped on the textField right away and forced a super quick read of the textField and all its properties which includes the border—from main thread (actual user touches are always handled through main thread)...which even though are not yet red on the screen, but since it's red on the model it will read from it and change color to red immediately.

Swift 3, want to perform timeout when using URLSession to load image from server

I'm using swift 3. I want to add timeout to URLSession when doing download task. I did use configuration to change my setting, however, it doesn't work. The code didn't perform timeout... If the server didn't response quickly, it will fail.
Here is my code:
import Foundation
import UIKit
extension UIImageView {
func loadImage(url: URL) -> URLSessionDownloadTask {
let session: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 70
configuration.timeoutIntervalForResource = 70
return URLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
}()
let downloadTask = session.downloadTask(with: url, completionHandler: { [weak self] url, response, error in
if error == nil, let url = url, let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
if let strongSelf = self {
strongSelf.image = image
}
}
}
})
downloadTask.resume()
return downloadTask
}
}
Any comment is appreciated!!

Swift 3 - Download JPEG image and save to file - macOS

I have a downloader class that downloads a file based on a given URL which then calls a completion passing it the contents of the file as NSData.
For the project that I'm using this in, the URL will be a JPEG image. The downloader works perfectly; I can use the result into NSImage and show it in a Image View Controller.
I would like to be able to save that NSData object to file.
After quite some time researching the internet on Google, StackOverflow, etc. and trying many suggestions, I cannot get the file to save.
Here is a playground of the Downloader class and my attempt to save the file:
//: Playground - noun: a place where people can play
import Cocoa
class NetworkService
{
lazy var configuration: URLSessionConfiguration = URLSessionConfiguration.default
lazy var session: URLSession = URLSession(configuration: self.configuration)
let url: NSURL
init(url: NSURL)
{
self.url = url
}
func downloadImage(completion: #escaping ((NSData) -> Void))
{
let request = NSURLRequest(url: self.url as URL)
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
if error == nil {
if let httpResponse = response as? HTTPURLResponse {
switch (httpResponse.statusCode) {
case 200:
if let data = data {
completion(data as NSData)
}
default:
print(httpResponse.statusCode)
}
}
} else {
print("Error download data: \(error?.localizedDescription)")
}
}
dataTask.resume()
}
}
let IMAGE_URL = NSURL(string: "https://www.bing.com/az/hprichbg/rb/RossFountain_EN-AU11490955168_1920x1080.jpg")
let networkService = NetworkService(url: IMAGE_URL!)
networkService.downloadImage(completion: { (data) in
data.write(to: URL(string: "file://~/Pictures/image.jpg")!, atomically: false)
})
The playground console show nothing at all. Can anyone spot why its not working?
NOTE: The target is macOS, not iOS. Also, I'm a swift noob...
I did try this:
networkService.downloadImage(completion: { (imageData) in
let imageAsNSImage = NSImage(data: imageData as Data)
if let bits = imageAsNSImage?.representations.first as? NSBitmapImageRep {
let outputData = bits.representation(using: .JPEG, properties: [:])
do {
try outputData?.write(to: URL(string: "file://~/Pictures/myImage.jpg")!)
} catch {
print("ERROR!")
}
}
})
It could be a permission issue. You may try:
let picturesDirectory = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask)[0]
let imageUrl = picturesDirectory.appendingPathComponent("image.jpg", isDirectory: false)
try? data.write(to: imageUrl)
It does work for me:

Swift 3 and NSURLSession issue

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")
}
}