I have a app that can download files from a webview. It was working fine in ios 12 but isnt working. I'm getting the error
Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
and
This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
This is my view.controller code:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
print(request.url as Any)
if request.url!.absoluteString.range(of: "/download/") != nil {
let extention = request.url!.absoluteString.slice(from: "&fileextension=", to: "&")?.lowercased()
var name = request.url!.absoluteString.slice(from: "&name=", to: "&")?.lowercased()
name = name?.replacingOccurrences(of: "+", with: " ")
DownlondFromUrl(request.url! as URL,name!, extention!)
return false
}
return true
}
func DownlondFromUrl(_ url: URL,_ name:String,_ extensionfile:String){
// Create destination URL
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let destinationUrl = documentsUrl!.appendingPathComponent(name + ".\(extensionfile)")
//Create URL to the source file you want to download
let fileURL = url
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
let dataFromURL = NSData(contentsOf: tempLocalUrl)
dataFromURL?.write(to: destinationUrl, atomically: true)
let alert = UIAlertController.init(title: "Download", message: "File download Successful. Do you want open file ", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Open", style: .default , handler:{ (UIAlertAction)in
let fileBrowser = FileBrowser()
self.present(fileBrowser, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Share", style: .default , handler:{ (UIAlertAction)in
let activityViewController = UIActivityViewController(activityItems: [destinationUrl], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler:{ (UIAlertAction)in
}))
self.present(alert, animated: true, completion: {
print("completion block")
})
} else {
print("Error took place while downloading a file. Error description: %#", error?.localizedDescription as Any);
}
}
task.resume()
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true;
}
}
I have found this post (Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread) but I'm very much a beginner and not sure how to implement this.
You have to run any code which accesses the UI on the main thread.
As URLSession tasks run on a background thread you have to add a DispatchQueue block
DispatchQueue.main.async {
let alert = UIAlertController.init(title: "Download", message: "File download Successful. Do you want open file ", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Open", style: .default , handler:{ action in
let fileBrowser = FileBrowser()
self.present(fileBrowser, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Share", style: .default , handler:{ action in
let activityViewController = UIActivityViewController(activityItems: [destinationUrl], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel)
self.present(alert, animated: true, completion: {
print("completion block")
})
}
Note:
The parameter in the UIAlertAction closure must be an instance not a type. If the parameter is not used you can replace it with an underscore character (for example handler:{ _ in)
Related
When downloading completed I'm showing the alert that download has been completed after that when user click on dismiss button in alert popup self.quickLook(url: url) func will call.
But not showing the file in webView. When removing the alert code everything working fine and file opening in webView.
func showAlerts(){
let alertController = UIAlertController(title: "Download", message: "Download Completed", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "dismiss", style: .default, handler: { _ in
self.dismiss(animated: true, completion: nil)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
#IBAction func openDoc(_ sender: UIButton) {
if let url = URL(string: "https://ikddata.ilmkidunya.com/images/books/12th-class-chemistry-chapter-10.pdf") {
self.loadFileAsync(url: url) { response, error in
if error == nil {
self.showAlerts()
self.quickLook(url: url)
}
}
}
}
Check console screenshot
Console screenshot see msg
How's your quickLook implemented? I guess it also calls present. You cannot present two things at the same time on iOS.
An option would be to quickLook after the alert is dismissed. Something like:
func showAlerts(and onDismiss: (() -> Void)? = nil) {
let alertController = UIAlertController(title: "Download", message: "Download Completed", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "dismiss", style: .default, handler: { _ in
self.dismiss(animated: true) { onDismiss?() }
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true, completion: nil)
}
#IBAction func openDoc(_ sender: UIButton) {
if let url = URL(string: "https://ikddata.ilmkidunya.com/images/books/12th-class-chemistry-chapter-10.pdf") {
loadFileAsync(url: url) { response, error in
if error == nil {
self.showAlerts { self.quickLook(url: url) }
}
}
}
}
Judging from your words it's exactly what you expect.
I want Alamofire to upload data to the server and depends on the result:
hide progress alert, dismiss current view controller, show
congratulations alert
hide progress alert, show error alert
My code works fine on iOS 9 and 11, but on iOS 10 on success case only hides progress alert. Users are confused and submit the form again and again
Alamofire.upload(multipartFormData: { MultipartFormData in
MultipartFormData.append((self.model.name?.data(using: String.Encoding.utf8)!)!, withName: "Form[name]")
MultipartFormData.append((self.model.category?.description.data(using: String.Encoding.utf8)!)!, withName: "Form[category]")
if (self.filePreview.count>0) {
for (index,preview) in self.filePreview.enumerated() {
if preview != nil
let data = UIImageJPEGRepresentation((preview?.getImage())!, 80)
MultipartFormData.append(data!, withName: "Form[files]["+String(describing:index)+"]", fileName: "attachment"+String(describing: index)+".JPG", mimeType: preview!.getMime())
}
}
}
debugPrint(MultipartFormData)
}, to: url, encodingCompletion: { (result) in
switch result {
case .success( _, _, _):
progressAlert.dismiss(animated: true, completion: nil)
self.dismiss(animated: true, completion: nil)
let doneAlert = UIAlertController(title: "Success", message: "Your message was sent", preferredStyle: .alert)
let donedOk = UIAlertAction(title: "OK", style: .default, handler: nil)
doneAlert.addAction(donedOk)
self.present(doneAlert, animated: true, completion: nil)
break
case .failure( _):
progressAlert.dismiss(animated: true, completion: nil)
let doneAlert = UIAlertController(title: "Failed", message: "Your message was not sent", preferredStyle: .alert)
let donedOk = UIAlertAction(title: "OK", style: .default, handler: nil)
doneAlert.addAction(donedOk)
self.present(doneAlert, animated: true, completion: nil)
break
}
})
Working with completition handlers and using UIApplication.shared.keyWindow?.rootViewController to find current top ViewController solved problem. Progress alert hides, current view controller dismisses, and success alert appears. Code:
progressAlert.dismiss(animated: true) {
let doneAlert = UIAlertController(title: "Отправлено", message: "Your message was sent", preferredStyle: .alert)
let donedOk = UIAlertAction(title: "Success", style: .default, handler: nil)
doneAlert.addAction(donedOk)
self.dismiss(animated: true) {
let presentingVC = UIApplication.shared.keyWindow?.rootViewController
presentingVC?.present(doneAlert, animated: true, completion: nil)
}
}
Issue with presenting a UIAlertController on the Controller whose view is not in the window hierarchy, firstly you are dismissing the controller & presenting UIAlertController on that dismissed controller.
You have to take a reference of controller on which the current controller is presenting, then after dismissing of current controller present UIAlertController on that previous one.
Update user interface on Main dispatch queue.
DispatchQueue.main.async {
let controller = self.presentingViewController
self.dismiss(animated: true) {
let doneAlert = UIAlertController(title: "Success", message: "Your message was sent", preferredStyle: .alert)
let donedOk = UIAlertAction(title: "OK", style: .default, handler: nil)
doneAlert.addAction(donedOk)
controller?.present(doneAlert, animated: true, completion: nil)
}
}
The order should be.
dismiss progress.
Show alert saying that everything is fine.
(this will obscure the interface so theres no possibility touch
anything behind ) When on step 2 tap over ok.
dismiss the upload controller.
Just to update code by rocky :
There´s no need to switch to MainQueue, Math Thompson on the resulting closure output to DispatchQueue.main.async.
So From my point of view interactions should be like :
switch result {
case .success( _, _, _):
//1
progressAlert.dismiss(animated: true, completion: nil)
//2
let doneAlertController = UIAlertController(title: "Success", message: "Your message was sent", preferredStyle: .alert)
let donedOk = UIAlertAction(title: "OK", style: .default) { (action) in
//3
self.dismiss(animated: true, completion: nil)
}
doneAlertController.addAction(donedOk)
self.present(doneAlertController, animated: true, completion: nil)
break
.
.
.
.
.
.
In iOS Swift 3.1 I'm trying to access the camera, the same way I have done successfully in other apps. However, in this one particular app, it always crashes on the self.present(imagePicker, animated: true, completion: nil) line. The message in the console is Message from debugger: Terminated due to signal 9. Does this usually signal a memory related error?
#IBAction func onChooseImageClick(_ sender: AnyObject) {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.savedPhotosAlbum){
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
//Create the AlertController and add Its action like button in Actionsheet
let actionSheetControllerForImage: UIAlertController = UIAlertController(title: "Please select", message: "Option to select", preferredStyle: .actionSheet)
let cancelActionButton: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) { action -> Void in
print("Cancel")
}
actionSheetControllerForImage.addAction(cancelActionButton)
let cameraActionButton: UIAlertAction = UIAlertAction(title: "Camera", style: .default)
{ action -> Void in
print("Camera")
if(UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera)) {
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
let mediaTypes:[String] = [kUTTypeImage as String]
imagePicker.mediaTypes = mediaTypes
imagePicker.allowsEditing = false
self.present(imagePicker, animated: true, completion: nil)
} else {
let alertController = UIAlertController(title: "error", message: "Camera not found!", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .cancel) { action in
// ...
}
alertController.addAction(OKAction)
self.present(alertController, animated: true)
}
}
actionSheetControllerForImage.addAction(cameraActionButton)
let galleryActionButton: UIAlertAction = UIAlertAction(title: "Image Gallery", style: .default)
{ action -> Void in
imagePicker.sourceType = UIImagePickerControllerSourceType.savedPhotosAlbum;
imagePicker.allowsEditing = false
self.present(imagePicker, animated: true, completion: nil)
}
actionSheetControllerForImage.popoverPresentationController?.sourceView = self.view
actionSheetControllerForImage.addAction(galleryActionButton)
===> self.present(actionSheetControllerForImage, animated: true, completion: nil)
}
}
You need a string in your plist file that explains why your app need permission to use the camera. There's a great summary of these strings in the question at iOS 10 - Changes in asking permissions of Camera, microphone and Photo Library causing application to crash, with a list you can copy from in the answer at https://stackoverflow.com/a/40734360/968577
Without the required strings, your app will crash in this manner. However, the console output will explain what the problem is.
Try like this i hope it would be helpful!!
Add these two points in info.plist first
Privacy - Camera Usage Description
Privacy - Photo Library Usage Description
Add these two delegates in your class file
UIImagePickerControllerDelegate
UINavigationControllerDelegate
#IBAction func onclickImageAction(_ sender: Any){
print("onclickImageAction method called here")
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.isEditing = false
let actionSheet = UIAlertController(title: "Select Profile Photo", message: "", preferredStyle: UIAlertControllerStyle.actionSheet)
let libButton = UIAlertAction(title: "Select photo from library", style: UIAlertActionStyle.default){ (libSelected) in
print("library selected")
imagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary
self.present(imagePicker, animated: true, completion: nil)
}
actionSheet.addAction(libButton)
let cameraButton = UIAlertAction(title: "Take a picture", style: UIAlertActionStyle.default) { (camSelected) in
if (UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera))
{
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
self.present(imagePicker, animated: true, completion: nil)
}
else
{
actionSheet.dismiss(animated: false, completion: nil)
let alert = UIAlertController(title: "Error", message: "There is no camera available", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Okay", style: .default, handler: { (alertAction) in
alert.dismiss(animated: true, completion: nil)
}))
}
}
actionSheet.addAction(cameraButton)
let cancelButton = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel) { (cancelSelected) in
print("cancel selected")
}
actionSheet.addAction(cancelButton)
let albumButton = UIAlertAction(title: "Saved Album", style: UIAlertActionStyle.default) { (albumSelected) in
print("Album selected")
imagePicker.sourceType = UIImagePickerControllerSourceType.savedPhotosAlbum
self.present(imagePicker, animated: true, completion: nil)
}
actionSheet.addAction(albumButton)
self.present(actionSheet, animated: true, completion:nil)
}
Implement these delegate method
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{if let PickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage
{
yourimageview.image = PickedImage
dismiss(animated: true, completion: nil)
}
}
I just added exit(0) when i opened the camera settings. It's working fine
The layout of project architecture:
record audio -> trim audio -> play trimmed audio -> upload to server.
I'm having troubles with playback of audio file that is created as a result of AVAssetExportSession trimming. I had doubts about integrity of trimmed file and I've uploaded it to server and there it plays fine, but iOS refuses to play it. I init AVAudioPlayer with URL to trimmed file, then play() and nothing happens, not even errors are thrown.
Please see code below, what can cause the problem?
static func outputFileURL() -> URL {
let outputFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path.appending("/audioRecord-trimmed.m4a")
return URL(fileURLWithPath: outputFileURL)
}
#IBAction func trimRecording(_ sender: RoundCornerButton) {
//Delete existing recording
deleteEditedRecording()
//Check duration
let duration = CMTimeGetSeconds(recordingToTrim.duration)
if (duration < 5.0) {
let alertController = UIAlertController(title: "Warning", message: "Sound is too short", preferredStyle: UIAlertControllerStyle.alert)
let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil)
alertController.addAction(action)
self.present(alertController, animated: true, completion: nil)
return
} else {
let exporter = AVAssetExportSession(asset: recordingToTrim, presetName: AVAssetExportPresetAppleM4A)
exporter?.outputFileType = AVFileTypeAppleM4A
exporter?.outputURL = EditorVC.outputFileURL()
exporter?.timeRange = durationToTrim!
exporter?.exportAsynchronously(completionHandler: {
if exporter?.status == .completed {
let alertController = UIAlertController(title: "Success", message: nil, preferredStyle: UIAlertControllerStyle.alert)
let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil)
alertController.addAction(action)
self.present(alertController, animated: true, completion: nil)
} else {
let alertController = UIAlertController(title: "Error", message: exporter?.error?.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil)
alertController.addAction(action)
self.present(alertController, animated: true, completion: nil)
print(exporter?.error?.localizedDescription)
print("Export failed")
return
}
})
}
}
#IBAction func playTrimmedAudio(_ sender: RoundCornerButton) {
print("\nPlay tap\n")
let player = try! AVAudioPlayer(contentsOf: EditorVC.outputFileURL())
player.play()
}
declare audio player globally like this :
var player:AVAudioPlayer!
in your playTrimmedAudio function add this two line of code
player = try! AVAudioPlayer(contentsOf: EditorVC.outputFileURL())
player.play()
I have a UIActionSheet for selecting between the camera or the photo library to embed an image into a UITextView but for whatever reason it's loading the keyboard. I force close the keyboard on press of the left button of the bar surrounding the UITextView but when I press photo library I opens and closes the keyboard before pushing to the image picker VC.
override func didPressLeftButton(sender: AnyObject?) {
let cameraMenu = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
let photoLibrary = UIAlertAction(title: "Photo Library", style: .Default, handler: { (UIAlertAction) in
self.openPhotoLibrary()
})
let takePhoto = UIAlertAction(title: "Open Camera", style: .Default, handler: { (UIAlertAction) in
self.textView.endEditing(true)
self.openCamera()
})
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
cameraMenu.addAction(photoLibrary)
cameraMenu.addAction(takePhoto)
cameraMenu.addAction(cancel)
self.presentViewController(cameraMenu, animated: true, completion: nil)
}
func openPhotoLibrary() {
imagePicker.sourceType = .PhotoLibrary
imagePicker.allowsEditing = false
presentViewController(imagePicker, animated: true, completion: nil)
}
func openCamera(){
imagePicker.sourceType = .Camera
imagePicker.showsCameraControls = true
presentViewController(imagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
// Image resizing
let textViewWidth: CGFloat = self.textView.frame.size.width - 20
let percentResize = textViewWidth / pickedImage.size.width
let toBeExportedHeight = pickedImage.size.height * percentResize
let resizedImage = ImageManipulationManager.sharedInstance.resizeImage(exportedWidth: Int(textViewWidth),exportedHeight: Int(toBeExportedHeight), originalImage: pickedImage)
// Storage into TextView
let attachment = NSTextAttachment()
attachment.image = resizedImage
let attString = NSAttributedString(attachment: attachment)
textView.textStorage.insertAttributedString(attString, atIndex: textView.selectedRange.location)
pastedImageLocations.append(textView.selectedRange.location)
textView.selectedRange.location = textView.selectedRange.location + 1
textView.textStorage.insertAttributedString(NSAttributedString(string: "\n"), atIndex: textView.selectedRange.location)
textView.selectedRange.location = textView.selectedRange.location + 1
textView.font = UIFont.systemFontOfSize(16.0)
// Image Caching
if let data = UIImageJPEGRepresentation(pickedImage, 0.50) {
socketMessages.append(["data": data])
haneke.set(value: data, key: String(unsafeAddressOf(attachment.image!)))
print("Image cached as \"\(String(unsafeAddressOf(attachment.image!)))\"")
}
}
dismissViewControllerAnimated(true, completion: nil)
self.textView.becomeFirstResponder()
}
Found the solution.
I had to change
dismissViewControllerAnimated(true, completion: nil)
self.textView.becomeFirstResponder()
to
dismissViewControllerAnimated(true) {
self.textView.becomeFirstResponder()
}
You can do some changes by adding this -
override func didPressLeftButton(sender: AnyObject?) {
let cameraMenu = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
let photoLibrary = UIAlertAction(title: "Photo Library", style: .Default, handler: { (UIAlertAction) in
self.view.endEditing(true) //**------ Add this
self.openPhotoLibrary()
})
let takePhoto = UIAlertAction(title: "Open Camera", style: .Default, handler: { (UIAlertAction) in
self.view.endEditing(true) //**------ Add this
self.openCamera()
})
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
cameraMenu.addAction(photoLibrary)
cameraMenu.addAction(takePhoto)
cameraMenu.addAction(cancel)
self.presentViewController(cameraMenu, animated: true, completion: nil)
}