I have 3 Labels and a UIButton in my view. After I press the UIButton some functions are being executed and I want them to do the following:
As soon as Picture 1 is finished downloading Label 1 shall be updated, after Picture 2 is finished downloading Label 2 shall be updated and so on.
The problem is: My view, so my labels, are all updated at once after all functions are resolved...
I read about asynchronous tasks - but in my understanding I don't need it because I don't mind if the user can't click any Buttons as long as all the functions are executed, all I want is the view(labels) to update while executing the functions, not only after everything is completed...
Here is my code:
#IBAction func ButtonLoadPictures(_ sender: UIButton) {
// Picture1
downloadImage()
LabelResult1.text = "Ready"
// Picture2
downloadImage()
LabelResult2.text = "Ready"
// Picture3
downloadImage()
LabelResult3.text = "Ready"
}
Edit:
Because I was asked for the code inside downloadImage():
downloadImage was just a placeholder for a lot of functions I have in this place. But in my understanding it is not relevant what the code specifically is. In case it is, I have added an example for downloadImage(). The problem is I don't want to update my labels all at once after execution of all functions, but after each time after "donwnloadImage()"
func downloadImage(){
guard let myURL = URL(string: myLink) else {
print("Error: \(myLink) doesn't seem to be a valid URL")
return
}
do {
myLinkHTMLcontent = try String(contentsOf: myURL, encoding: .ascii)
print(myLinkHTMLcontent)
} catch let error {
print("Error: \(error)")
}
The path I would choose in this case is asynchronous tasks in your logic, and use completion handlers, something like this:
IBAction func ButtonLoadPictures(_ sender: UIButton) {
// Picture1
downloadImage(){ result in
self.LabelResult1.text = result
}
// Picture2
downloadImage(){ result in
self.LabelResult2.text = result
}
// Picture3
downloadImage(){ result in
self.LabelResult3.text = result
}
}
func downloadImage(completion: #escaping(String) -> ()){
// all your logic
completion("ready")
}
Related
I'm making an API where the user can submit items to be processed, and they might want to check whether their item was processed successfully. I thought that this would be a good place to use tokio::sync::oneshot channels, where I'd return the receiver to the caller, and they can later await on it to get the result they're looking for.
let processable_item = ...;
let where_to_submit: impl Submittable = get_submit_target();
let status_handle: oneshot::Receiver<SubmissionResult> = where_to_submit.submit(processable_item).await;
// ... do something that does not depend on the SubmissionResult ...
// Now we want to get the status of our submission
let status = status_handle.await;
Submitting the item involves creating a oneshot channel, and putting the Sender half into a queue while the Receiver goes back to the calling code:
#[async_trait]
impl Submittable for Something {
async fn submit(item: ProcessableItem) -> oneshot::Receiver<SubmissionResult> {
let (sender, receiver) = oneshot::channel();
// Put the item, with the associated sender, into a queue
let queue: mpsc::Receiver<(ProcessableItem, oneshot::Sender<SubmissionResult>)> = get_processing_queue();
queue.send( (item, sender) ).await.expect("Processing task closed!");
return receiver;
}
}
When I do this, cargo clippy complains (via the [clippy::async_yields_async] lint) that I'm returning oneshot::Receiver, which can be awaited, from an async function, and suggests that I await it then.
This is not what I wanted, which is to allow a degree of background processing while the user doesn't need the SubmissionResult yet, as opposed to making them wait until it's available.
Is this API even a good idea? Does there exist a common approach to doing this?
Looks fine to me. This is a false positive of Clippy, so you can just silence it: #[allow(clippy::async_yields_async)].
I'm trying to get all files from firebase's storage through listAll.
By the way..
storageReference.listAll().addOnSuccessListener { listResult ->
val image_task : FileDownloadTask
for (fileRef in listResult.items) {
fileRef.downloadUrl.addOnSuccessListener { Uri ->
image_list.add(Uri.toString())
println("size1 : " + image_list.size)
}
}
println("size2 : " + image_list.size)
}//addOnSuccessListener
enter image description here
Why is the execution order like this?
How do I solve it??
When you add a listener or callback to something, the code inside the listener will not be called until sometime later. Everything else in the current function will happen first.
You are adding listeners for each item using your for loop. No code in the listeners is running yet. Then your "size2" println call is made after the for loop. At some later time, all your listeners will fire.
If you want asynchronous code like this to be written sequentially, then you need to use coroutines. That's a huge topic, but your code would look something like this (but probably a little more involved than this if you want to properly handle errors). I'm using lifecycleScope from an Android Activity or Fragment for this example. If you're not on Android, you need to use some other CoroutineScope.
The calls to await() are an alternative to adding success and failure listeners. await() suspends the coroutine and then returns a result or throws an exception on failure.
lifecycleScope.launch {
val results = try {
storageReference.listAll().await()
} catch (e: Exception) {
println("Failed to get list: ${e.message}")
return#launch
}
val uris = try {
results.map { it.downloadUrl.await().toString() }
} catch (e: Exception) {
println("Failed to get at least one URI: ${e.message}")
return#launch
}
image_list.addAll(uris)
}
There is nothing wrong with the execution order here.
fileRef.downloadUrl.addOnSuccessListener { Uri ->
the downloadUrl is an asynchronous action which means it doesn't wait for the action to actually complete in order to move along with the code.
You receive the result with the success listener (at least in this case)
If you want to deal with it in a sequential way, look at coroutines.
I have an apple watch complication and the iPhone app running side by side. I have a button within the app to transmit application context dictionary to the watch. I expect to see the complication title to be refreshed.
I cannot seem to force the "tap button -> see update on the complication" kind of behavior.
What is the appropriate method to force a complication update? How can I refresh my apple watch complication instantly?
I do see the title changes, but I think it requires me to tap on the complication to open it's apple watch app first. How can I get the complication to update itself on the Watch home screen?
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
if complication.family == .graphicRectangular {
let template = CLKComplicationTemplateGraphicRectangularLargeImage()
//...configure
return template
}
}
I see this apple provided code that refreshes the complication. I'm not sure if it is too much, or if calling extendTimeline alone is sufficient if I'm generating the complication using the entry above.
func refreshComplication() {
#if os(watchOS)
let server = CLKComplicationServer.sharedInstance()
if let complications = server.activeComplications {
for complication in complications {
// Call this method sparingly. If your existing complication data is still valid,
// consider calling the extendTimeline(for:) method instead.
server.reloadTimeline(for: complication)
}
}
#endif
}
You should be able to do this by calling the refreshComplication() function from your didReceiveApplicationContext block in the file which has your WCSessionDelegate.
So if you are receiving the title via an applicationContext message your code would look something along these lines.
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
if let updatedTitle = applicationContext["updatedTitle"] {
if let title = updateTitle as? String {
//Remeber that complicationServer.swift is a seperate process therefore you will need to store the received data somehow.
UserDefaults.standard.set(title, forKey: "complicationTitle")
refreshComplication()
}
}
}
I have a setting in my iOS App that lets the user change their target and using this method refreshed the complication with the new target almost instantly. However, I believe once your complication has used up its cpu budget nothing will happen, but hopefully that is not happening for you. See https://developer.apple.com/documentation/clockkit/clkcomplicationserver/1627891-reloadtimeline
Hope that helps, let me know how you get on.
Drew
I am trying to get the depth data associated with an image in the PhotoLibrary.
I can get the image, and the URL, but I can't seem to get the aux data associated with it. The call to CGImageSourceCreateWithURL returns a source, but the call to CGImageSourceCopyAuxiliaryDataInfoAtIndex returns nil for both kCGImageAuxiliaryDataTypeDisparity and kCGImageAuxiliaryDataTypeDepth.
Is there something I am missing here?
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let image = info[UIImagePickerControllerOriginalImage]
let url = info[UIImagePickerControllerImageURL]
print("url=",url)
guard let source = CGImageSourceCreateWithURL(url as! CFURL, nil) else {
return
}
guard let auxDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) as? [AnyHashable : Any] else {
return
}
}
I struggled with this for a whole day! I finally figured it out, though, after watching the first half of the WWDC Video titled "Editing Images With Depth."
My problem was using a URL for the image that was not from the PHAsset.
Here is the link:
LINK TO WWDC VIDEO
If you don't feel like watching it, check out this function that I wrote that does pretty much exactly what is done in the video.
You have to provide the function the [info] that is returned from the DID_FINISH_PICKING_IMAGE_WITH_INFO function from the UIImagePickerDelegate.
Before using this function - note that is actually doesn't work! It is great to look at though, because it shows the steps clearly. But due to asynchronous behavior, the function will always return nil before it has a chance to set the local depth variable to the AVDepthData.
My solution was to break the function apart and use Grand Central Dispatch to create a Dispatch Group, enter it, retrieve the imageURL from the PHAsset, and then leave the Dispatch Group. Upon leaving the Dispatch Group, the DispatchGroup.NOTIFIED function then proceeded with the rest of the process.
I hope this helps!!!
func returndepthdata(usingimageinfo: [UIImagePickerController.InfoKey : Any]) -> AVDepthData? {
var depthdata: AVDepthData! = nil
if let photoasset = usingimageinfo[.phAsset] as? PHAsset {
let input = photoasset.requestContentEditingInput(with: nil, completionHandler: { (input, info) in
if let imageurl = input?.fullSizeImageURL {
if let source = CGImageSourceCreateWithURL(imageurl as CFURL, nil) {
if let imageproperties = CGImageSourceCopyProperties(source, nil) {
if let disparityinfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) {
if let truedepthdata = try? AVDepthData(fromDictionaryRepresentation: disparityinfo as! [AnyHashable : Any]) {
depthdata = truedepthdata
}
}
}
}
}
})
}
return depthdata
}
The image URL supplied by UIImagePickerController does not include any of the metadata associated with depth. To get this information, you must access the PHAsset using the PhotoBook API.
First, import the API:
import Photos
Before you display your image picker, request user access to the photo book. You'll need to add an info dictionary key for Photo Library Usage for this to work:
switch PHPhotoLibrary.authorizationStatus() {
case .notDetermined:
PHPhotoLibrary.requestAuthorization { (status) in
if status == .authorized {
DispatchQueue.main.async {
// Display image picker here
}
}
}
case .authorized: // Display image picker here
case .denied, .restricted: // Display appropriate error here
}
Now, in your image picker delegate you can do this:
if let asset = info[.phAsset] as? PHAsset {
PHImageManager.default().requestImageData(for: asset, options: nil) { (imageData, dataType, orientation, info) in
let url = info?["PHImageFileURLKey"] as? URL
// Pass this URL to your existing code.
}
}
Note that the file may contain depth or disparity information. You can convert between these easily enough, but you may need to check which one you have using CGImageSourceCopyProperties(). Also look out for the new supplementary depth data, kCGImageAuxiliaryDataTypePortraitEffectsMatte, which gives a much higher resolution mask for just the subject person in portrait images and is great for doing greenscreen-style effects.
I currently havea widget which grabs hundreds of documents from the DB via subscription and then keep listening for new documents, so it can update a stock chart.
There is a problem tough, which is every time the data is updated the chart is updated, which causes a redraw.
This is a problem cause it's calling redraw hundreds of time at the beginning even tough it just need to "fetch all data then draw and wait for updates", the updates will then happen not very often, so then it would be ok to redraw.
my current code:
Template.nwidget.onRendered(function() {
return this.autorun(function() {
var data;
data = {};
data = Data.find({
type: 'my_type'
});
data = data.fetch();
return update(data);
});
});
For doing some after data subscription you can do like this:
Meteor.subscribe( 'collection', {
onStop: function( error /* optional */ ) {
// when the sub terminates for any reason,
// with an error argument if an error triggered the stop
},
onReady: function() {
// when ready
}
});
If you want to render page after the data subcribe then you can add waitOn in your router.
There is one more way to check where subscription is ready or not. If subscription is not ready you can show something else like a loading screen.
var handle = Meteor.subscribe( 'collection');
Tracker.autorun(function() {
if (handle.ready())
//write whatever you want to do here.
});
For the auto update in your view you can store the date in a reactive thing its may reactive var, Session or collection.
Then you can return there values from helper to view. And that will auto update your view.