How to work with DispatchQueue.global? - swift3

I am downloading images from my server using this
let queue = DispatchQueue.global(qos: .userInteractive)
queue.async{ ...
And while images downloading user can tap the button. But sometimes this button doesn't work (when all images done button work correct).
How to solve this problem?

Donot access global queue for that, instead create a custom DispatchQueue with label, qos as .background and fetch the image, when done use DispatchQueue.main to return the image to the main queue
For e.g:
let queue = DispatchQueue(label: "myImageQueue", qos: .background)
queue.async {
...fetch image
DispatchQueue.main.async {
self.imageView.image = fetchedImage
}
}

Related

SwiftUI sharing application data using pasteboard

I have a phone / iPad application. It would be useful to export text, images, or other data to other apps or other devices.
I can export the colour data as text using UIpasteboard.general.string. This works nicely. The Universal pasteboard lets me paste into other devices.
I can export an image using UIpasteboard.general.image. I have a PNG image in my bundle resources. I can the paste it into Preview or some other image viewer. I would like to be able to paste it as a file but I can't see how to do that at the other end.
I have also a PDF manual in the Bundle. It would be nice to export that too. Like the previous image, it would be nice to paste it as a file but I cannot see how this is done.
Before I get into the code, is UIpasteboard the right way to do this? Or is there some other approach I ought to be using rather than trying to fix this. What do other apps do?
Grid(horizontalSpacing: 3, verticalSpacing:3) {
GridRow {
Text("Text: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
let pasteboard = UIPasteboard.general
pasteboard.string = toString(sw: sd.mySwatch)
} label: {
Text("Current swatch").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
GridRow {
Text("Text: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
var reply: String = ""
for sw in sd.swatches {
reply = reply + toString(sw: sw)
}
UIPasteboard.general.string = reply
} label: {
Text("All swatches").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
GridRow {
Text("Image: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
UIPasteboard.general.image = UIImage(contentsOfFile: Bundle.main.path(forResource: "Macbeth", ofType: "png")!)
} label: {
Text("Macbeth.png").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
GridRow {
Text("PDF: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
UIPasteboard.general.url = Bundle.main.url(forResource: "ByEye", withExtension: "pdf")!
} label: {
Text("ByEye.pdf").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
}
That is my code. The first three options work. The last one does not because the URL is a private one for the application. I might be able to copy the file to a public directory so the URL worked but that seems like the wrong way to go about it.
Postscript:
This actually works well enough for now. I can copy stuff to the pasteboard, then paste it into an e-mail or other message to myself. This lets me get stuff out of the app without creating temporary files the I have to clean up.
I did not find a solution for the PDF or other files. I was hoping for something like this...
UIPasteboard.general.file = UIImage(contentsOfFile: Bundle.main.path(forResource: "manual", ofType: "pdf")!)
...but nothing like that exists as far as I can tell.

Show downloaded mp4 inside a view like VStack and play it AVPlayer, SwiftUI

I'm working on the offline downloads section for a video streaming app. Currently, I'm able to download videos and also can see the file path for the downloads as follows:
Button(action: {
let fileManager = FileManager.default
do {
let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let enumerator = FileManager.default.enumerator(at: documentsURL,
includingPropertiesForKeys: resourceKeys,
options: [.skipsHiddenFiles], errorHandler: { (url, error) -> Bool in
print("directoryEnumerator error at \(url): ", error)
return true
})!
for case let fileURL as URL in enumerator {
let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
// print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
print(fileURL.path)
self.array.append(fileURL.path)
}
} catch {
print(error)
}
}){
Text("Check downloads")
}
//response when button is clicked
/private/var/mobile/Containers/Data/Application/AD90B554-3465-43DD-BAE2-04BC83F850A3/Documents/Bz75srG5nctDCnAWIspM.mp4
/private/var/mobile/Containers/Data/Application/AD90B554-3465-43DD-BAE2-04BC83F850A3/Documents/GWYgrgDVYaowxeGcOjzp.mp4
Since I've renamed the mp4 files using movie ID's (eg: GWYgrgDVYaowxeGcOjzp.mp4) my goal is to use this name to show which videos were downloaded and allow users to play the video. I'm able to do the name processing later on, but how am I able to get the downloaded mp4 files into a view like a VStack? I was thinking of looping through and adding the files into an array but wanted to check if there's a different way. Also, would be great if you could show me how to play a video using the path as the url. I've tried applying suggested methods but ended up with a "NSURLConnection finished with error - code -1002" error. Thank you!

List addAll throw error :Unhandled Exception: Concurrent modification during iteration:

What is actually happening?
main.dart
In initState, we are calling server to get image from database, then add it into sink
var capturedImagesList = List<dynamic>();
#override
void initState() {
super.initState();
_bloc.getImages(); //
});
}
bloc class
final _urlImage = BehaviorSubject<List<dynamic>>();
get urlImageSink => _urlImage.sink;
get urlImageStream => _urlImage.stream;
Future getImages() async {
Response image = await _repo.getImages(); // call server
var imageResponse = Response.fromJson(image.body);
urlImageSink.add(imageResponse.images); // add image to sink
}
The retrieved image will be displayed in horizontal ListView.
When the + image is clicked, it will allow user to select an image from gallery, then placed beside the ListView as image below.
Everything works fine !
But when I click the + image again to add one more image, it throw us this error
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception:
Concurrent modification during iteration: Instance(length:3) of
'_GrowableList'. E/flutter (32313): #0 List.addAll
(dart:core-patch/growable_array.dart:187:11)
main.dart
This is the code after image is selected from gallery.
#override
List<dynamic> userImage(File images) {
if (images != null) {
capturedImagesList.add(images.path); // add selected image to List
capturedImagesList.addAll(_bloc.urlImageStream.value); // add the server image to list
_bloc.urlImageSink.add(capturedImagesList); // add all images to list
}
return null;
}
Errror is pointed to this line
capturedImagesList.addAll(_bloc.urlImageStream.value);
Why it works for the first time, but failed for second time?
You could just do that
_bloc.urlImageSink.add([images])
And the image would reflected immediately in UI.
I think the problem is in this line
capturedImagesList.addAll(_bloc.urlImageStream.value);
this looks strange, you can't copy the stream into array, it won't work.
Did you check out this thread: Exception: Concurrent modification during iteration: Instance(length:17) of '_GrowableList'
I quote from the article:
This error means that you are adding or removing objects from a collection during iteration. This is not allowed since adding or removing items will change the collection size and mess up subsequent iteration.
So I don't see all the code, but could it be that when you add a picture via the add button, you clear the list or remove the current items?
Try to clear the array like this before doing addAll
_myLeaveRequests?.clear();

Audio Controls on Lock Screen/Control Center From AV player Issues

Im new to swift and Im having issues while using MPRemoteCommandCenter.
When I try to control the audio settings, previously accessed songs also start playing along with the current song.
Im trying to access using the following code:
UIApplication.shared.beginReceivingRemoteControlEvents()
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.pauseCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
//Update your button here for the pause command
self.player!.pause()
return .success
}
commandCenter.playCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
//Update your button here for the play command
self.player!.play()
return .success
}
I have also used the following while ending the view:
UIApplication.shared.endReceivingRemoteControlEvents()

Posting with UIActivityViewController, but avoiding cropping?

Say you construct an image that is fullscreen on different devices. You then use UIActivityViewController to post to - for example - Instagram in the normal way.
The user clicks your share button, it brings up the usual iOS-sharing-thingy,
and you can post to Instagram (assuming the user's an Instagram user of course). No worries.
But typically the image is cropped on Instagram - you lose a little of the top and bottom.
Is there actually any solution for this?
Note that indeed - say you open the normal Photos app on the iPhone, and "share" and post on Instagram ... you lose a little of the top and bottom!
When the user does click the Instagram icon on this ...
in fact is there a way for me then to go back, be aware of the user's choice, and make the image the appropriate size?
Is there perhaps a way to pass a selection of images (various sizes) to the UIActivityViewController?
What's the deal on this, it seems like a basic failing?
Note - I'm fully aware that BEFORE going to the iOS-share-thingy, I could ask the user myself "What size image would you like me to make?"
Note - I'm aware that it's in some cases possible to post "directly" to say Instagram inside the app, without using Apple's share system; that's lame though.
To save anyone typing, here's some clean code to bring up the iOS-share system...
#IBAction func userClickedOurShareButton()
{
let s:[AnyObject] = [buildImage()]
let ac = CleanerActivity(activityItems:s, applicationActivities:nil)
ac.popoverPresentationController?.sourceView = view
// needed so that iPads won't crash. sarcasm: thanks Apple
ac.excludedActivityTypes = [UIActivityType.assignToContact,
UIActivityType.saveToCameraRoll,
UIActivityType.addToReadingList,
UIActivityType.copyToPasteboard ]
// consider UIActivityTypeMessage also
if #available(iOS 9.0, *) {
ac.excludedActivityTypes?.append(UIActivityType.openInIBooks)
} else {
// Fallback on earlier versions
}
self.present(ac, animated:false, completion:nil)
}
class CleanerActivity: UIActivityViewController {
func _shouldExcludeActivityType(_ activity: UIActivity) -> Bool {
let activityTypesToExclude = [
"com.apple.reminders.RemindersEditorExtension",
"com.apple.mobilenotes.SharingExtension",
"com.google.Drive.ShareExtension",
"com.apple.mobileslideshow.StreamShareService"
]
if let actType = activity.activityType {
if activityTypesToExclude.contains(actType.rawValue) {
return true
}
else if super.excludedActivityTypes != nil {
return super.excludedActivityTypes!.contains(actType)
}
}
return false
}
Disclaimer: this solution involves hard-coding Instagram's extension identifier into your app, which may or may not make it through app review, and may break in the future. Try at your own risk!
Apple provides a mechanism for this called UIActivityItemProvider. Instead of passing an image to your UIActivityViewController, you can pass subclass of UIActivityItemProvider that overrides itemForActivityType to return an appropriate image based on the activity type chosen by the user.
Apple provides constants for many common activity types, but Instagram isn't yet included. You can identify Instagram by checking if the activity type's raw value is com.burbn.instagram.shareextension. This would break if Instagram changed the ID of their extension.
Here's an UIActivityItemProvider that provides different images to Instagram:
class DynamicImageProvider: UIActivityItemProvider {
let instagramImage: UIImage
let defaultImage: UIImage
init(instagramImage: UIImage, defaultImage: UIImage) {
self.instagramImage = instagramImage
self.defaultImage = defaultImage
super.init(placeholderItem: defaultImage)
}
override func activityViewController(_ activityViewController: UIActivityViewController,
itemForActivityType activityType: UIActivityType) -> Any? {
if activityType.rawValue == "com.burbn.instagram.shareextension" {
return instagramImage
}
else {
return defaultImage
}
}
}
Then change the first two lines of your IBAction:
let imageProvider = DynamicImageProvider(instagramImage:buildInstagramImage(), defaultImage:buildImage())
let ac = CleanerActivity(activityItems:[imageProvider], applicationActivities:nil)