Per this tutorial I have set up a simple "record" UIButton, where I am taken to the camera, can record video only, and after taking the video, am taken to the video editor screen where I can trim. Once trimmed, I hit save and the video successfully saves to my camera roll. However, it is only saving the untrimmed video. (update below, with the UIVideoEditorController, I now get 2 unedited videos upon saving).
Code as follows:
VideoHelper.swift
import MobileCoreServices
import UIKit
import AVFoundation
enum VideoHelper {
static func startMediaBrowser(
delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate,
sourceType: UIImagePickerController.SourceType
) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType)
else { return }
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType //checks to see if a movie exists on the camera roll, the camera itself, and the photo library
mediaUI.mediaTypes = [kUTTypeMovie as String] //allows only movies to be selected
mediaUI.allowsEditing = true
mediaUI.delegate = delegate
delegate.present(mediaUI, animated: true, completion: nil)
}
}
RecordVideoViewController.swift
import UIKit
import MobileCoreServices //contain predefined constants such as kUTTypeMovie which lets you select only video
import Photos
import AVFoundation
class RecordVideoViewController: UIViewController {
#IBAction func record(_ sender: AnyObject) {
VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera) //opens the image picker and chooses the camera itself
}
}
//MARK: - UIImagePickerControllerDelegate
extension RecordVideoViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
dismiss(animated: true, completion: nil)
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
//1 gives you the URL pointing to the video
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL,
//2 verify the app can save the file to the device's photo album
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path)
else { return }
//launches video editor
guard UIVideoEditorController.canEditVideo(atPath: url.path) else { return }
let editor = UIVideoEditorController()
editor.videoPath = url.path
editor.videoMaximumDuration = 10.0
editor.videoQuality = .typeIFrame1280x720
present(editor, animated: true, completion: nil)
//3 if it can, save it
UISaveVideoAtPathToSavedPhotosAlbum(
url.path,
self,
#selector(video(_:didFinishSavingWithError:contextInfo:)),
nil)
}
//Displays an alert announcing whether the video file was saved or not, based on the error status
#objc func video(
_ videoPath: String,
didFinishSavingWithError error: Error?,
contextInfo info: AnyObject
) {
let title = (error == nil) ? "Success" : "Error"
let message = (error == nil) ? "Video was saved" : "Video failed to save"
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: UIAlertAction.Style.cancel,
handler: nil))
present(alert, animated: true, completion: nil)
}
}
//MARK: - UINavigationControllerDelegate
extension RecordVideoViewController: UINavigationControllerDelegate {
}
//MARK: - UIVideoEditorControllerDelegate
extension RecordVideoViewController: UIVideoEditorControllerDelegate {
}
I must be close as I have a successful video, it's just I only want the trimmed version.
Update to RecordVideoViewController.swift
I have now added the UIVideoEditorDelegate, and instantialized the delegate, as well as set the delegate and implemented the delegate methods. It now results in two saved videos, of which none are the edited one.
import UIKit
import MobileCoreServices //contain predefined constants such as kUTTypeMovie which lets you select only video
import Photos
import AVFoundation
class RecordVideoViewController: UIViewController {
#IBAction func record(_ sender: AnyObject) {
VideoHelper.startMediaBrowser(delegate: self, sourceType: .camera) //opens the image picker and chooses the camera itself
}
}
//MARK: - UIImagePickerControllerDelegate
extension RecordVideoViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
dismiss(animated: true, completion: nil)
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
//1 gives you the URL pointing to the video
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL,
//2 verify the app can save the file to the device's photo album
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(url.path),
//****2.5? Totally of the rails fromt he tutorial on this one:*****
UIVideoEditorController.canEditVideo(atPath:url.path)
else { return }
//Must instantiate the UIVideoEditorController with "let editor = UIVideoEditorController()" and set the instance's delegate via self as well as show the file path
let editor = UIVideoEditorController()
editor.delegate = self
editor.videoPath = url.path
//presents the UIVideoEditorController
self.present(editor, animated: true)
print(editor.modalPresentationStyle.rawValue)
}
// The UIVideoController's interface on the phone shows cancel and save buttons, which "do not" dismiss the presented view. Must do that in the implementation of the delegate methods, of which there are 3. And all 3 methods must be told to dismiss the presented view.
// Delegate Method #1 (didSaveEditedVideoToPath): Called when the system has finished saving an edited movie. At this point, the the trimmed video has already been saved to a file in app's temporary directory
func videoEditorController(_ editor: UIVideoEditorController, didSaveEditedVideoToPath path: String) {
self.dismiss(animated:true)
}
//Delegate Method #2 (videoEditorControllerDidCancel): Called when the user has cancelled a movie editing operation
func videoEditorControllerDidCancel(_ editor: UIVideoEditorController) {
self.dismiss(animated:true)
}
//Delegate Method #3 (didFailWithError): Called when the video editor is unable to load or save a movie. Important as things "can" fail at this point. MMhmmm
func videoEditorController(_ editor: UIVideoEditorController, didFailWithError error: Error) {
self.dismiss(animated:true)
//***back on the rails with the tutorial, hope this works***
//3 if it can, save it
UISaveVideoAtPathToSavedPhotosAlbum(
editor.videoPath,
self,
#selector(video(_:didFinishSavingWithError:contextInfo:)),
nil)
}
//Displays an alert announcing whether the video file was saved or not, based on the error status
#objc func video(
_ videoPath: String,
didFinishSavingWithError error: Error?,
contextInfo info: AnyObject
) {
let title = (error == nil) ? "Success" : "Error"
let message = (error == nil) ? "Video was saved" : "Video failed to save"
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: UIAlertAction.Style.cancel,
handler: nil))
present(alert, animated: true, completion: nil)
}
}
//MARK: - UINavigationControllerDelegate
extension RecordVideoViewController: UINavigationControllerDelegate {
}
//MARK: - UIVideoEditorControllerDelegate
extension RecordVideoViewController: UIVideoEditorControllerDelegate {
}
Code in iOS does not magically "wait" for something to happen. So, when you say this:
let editor = UIVideoEditorController()
// ...
present(editor, animated: true, completion: nil)
UISaveVideoAtPathToSavedPhotosAlbum(
...you present the editor, but instead of waiting for the user to see the editor and do the trim, you immediately just go on to save the video as you originally have it.
So how do you wait? Well, look at the UIVideoEditorController docs. It has a delegate. You must set that delegate (you cannot use a video editor controller without one), and you must implement the delegate method in your delegate. As a result that method will be called — after the user edits! And when it is called it is handed the edited video! So that is where you proceed to save.
Related
I sent a user a TestFlight version of my app. She has a video recorded that she made using her iPhone. When the imagePicker was presented and she selected a video it came back as nil. I have an error message with a number 120 that appears in an alert that tells me where the error occurred.
It can only be the asset [UIImagePickerController.InfoKey.phAsset] as? PHAsset is nil. I don't see how that's possible because it's a video that she made using her phone. The odd thing is when she selects a photo everything works fine and when I select videos using iOS 14 and iOS 13 everything works fine.
She's on iOS 15.1, and I'm wondering is that the issue? She's a fellow iOS dev and she said iOS 15 has been causing issues.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset
// ...
if let style = asset?.playbackStyle {
// ...
// if it entered here then the below alert would have never appeared
} else {
let errorMessage = "Error: 120"
let alert = UIAlertController(title: "Unknown Error", message: errorMessage, preferredStyle: .alert)
// ...
}
imagePicker?.dismiss(animated: true, completion: nil)
}
FYI afterwards I just tested on iOS 15.1 with some videos and it worked fine.
I tried the PHPickerController but it's very buggy so I'd rather stick with the ImagePicker for now.
I'm not exactly sure how the asset can be nil but according to this answer by #Sh_Kahn I need to handle all possibilities:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset {
let style = asset.playbackStyle
// ...
} else if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL {
// ...
} else if let imageUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL {
// ...
} else {
let errorMessage = "Error: 120"
let alert = UIAlertController(title: "Unknown Error", message: errorMessage, preferredStyle: .alert)
// ...
}
imagePicker?.dismiss(animated: true, completion: nil)
}
I'm newbie in iOS development, so some things which I will show and ask here can be stupid and please don't be angry :) So, I need to add support of picking files from local storage in my app. This feature will be used for picking file -> encoding to Base64 and then sending to remote server. Right now I have some problems with adding this functionality to my app. I had found this tutorial and did everything what was mentioned here:
added import - import MobileCoreServices
added implementation - UIDocumentPickerDelegate
added this code scope for showing picker:
let documentPicker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeText),String(kUTTypeContent),String(kUTTypeItem),String(kUTTypeData)], in: .import)
documentPicker.delegate = self
self.present(documentPicker, animated: true)
and also added handler of selected file:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print(urls)
}
In general file chooser appears on simulator screen, but I see warning in XCode:
'init(documentTypes:in:)' was deprecated in iOS 14.0
I visited the official guideline and here also found similar info about deprecation some method. So, how I can solve my problem with file choosing by the way which will be fully compatible with the latest iOS version. And another question - how I can then encode selected file? Right now I have an ability of file choosing and printing its location, but I need to get its data like name, content for encoding and some others. Maybe someone faced with similar problems and knows a solution? I need to add it in ordinary viewcontroller, so when I tried to add this implementation:
UIDocumentPickerViewController
I saw such error message:
Multiple inheritance from classes 'UIViewController' and 'UIDocumentPickerViewController'
I will be so pleased for any info: tutorials or advice :)
I decided to post my own solution of my problem. As I am new in ios development my answer can contain some logical problems :) Firstly I added some dialogue for choosing file type after pressing Attach button:
#IBAction func attachFile(_ sender: UIBarButtonItem) {
let attachSheet = UIAlertController(title: nil, message: "File attaching", preferredStyle: .actionSheet)
attachSheet.addAction(UIAlertAction(title: "File", style: .default,handler: { (action) in
let supportedTypes: [UTType] = [UTType.png,UTType.jpeg]
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes)
documentPicker.delegate = self
documentPicker.allowsMultipleSelection = false
documentPicker.shouldShowFileExtensions = true
self.present(documentPicker, animated: true, completion: nil)
}))
attachSheet.addAction(UIAlertAction(title: "Photo/Video", style: .default,handler: { (action) in
self.chooseImage()
}))
attachSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(attachSheet, animated: true, completion: nil)
}
then when a user will choose File he will be moved to ordinary directory where I handle his selection:
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
var selectedFileData = [String:String]()
let file = urls[0]
do{
let fileData = try Data.init(contentsOf: file.absoluteURL)
selectedFileData["filename"] = file.lastPathComponent
selectedFileData["data"] = fileData.base64EncodedString(options: .lineLength64Characters)
}catch{
print("contents could not be loaded")
}
}
as you can see in scope above I formed special dicionary for storing data before sending it to a server. Here you can also see encoding to Base64.
When the user will press Photo/Video item in alert dialogue he will be moved to gallery for picture selecting:
func chooseImage() {
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImageData = [String:String]()
guard let fileUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL else { return }
print(fileUrl.lastPathComponent)
if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
selectedImageData["filename"] = fileUrl.lastPathComponent
selectedImageData["data"] = pickedImage.pngData()?.base64EncodedString(options: .lineLength64Characters)
}
dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
via my method all file content will be encoded to base64 string.
P.S. Also I'm so pleased to #MaticOblak because he showed me the initial point for my research and final solution. His solution also good, but I have managed to solve my problem in way which is more convenient for my project :)
As soon as you have file URL you can use that URL to retrieve the data it contains. When you have the data you can convert it to Base64 and send it to server. You gave no information about how you will send it to server but the rest may look something like this:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
and you would use it as
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
urls.forEach { sendFileWithURL($0) { <#Your code here#> } }
}
To break it down:
To get file data you can use Data(contentsOf: url). This method even works on remote files so you could for instance use an URL of an image link anywhere on internet you have access to. It is important to know that this method will pause your thread which is usually not what you want.
To avoid breaking the current thread we create a new queue using DispatchQueue(label: "DownloadingFileData." + UUID().uuidString). The name of the queue is not very important but can be useful when debugging.
When data is received we convert it to Base64 string using data.base64EncodedString() and this data can then be sent to server. You just need to fill in the TODO: part.
Retrieving your file data can have some errors. Maybe access restriction or file no longer there or no internet connection... This is handled by throwing. If the statement with try fails for any reason then the catch parts executes and you receive an error.
Since all of this is done on background thread it usually makes sense to go back to main thread. This is what the finish function does. If you do not require that you can simply remove it and have:
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
DispatchQueue(label: "DownloadingFileData." + UUID().uuidString).async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
completion(nil)
} catch {
completion(error)
}
}
}
There are other things to consider in this approach. For instance you can see if user selects multiple files then each of them will open its own queue and start the process. That means that if user selects multiple files it is possible that at some point many or all of them will be loaded in memory. That may take too much memory and crash your application. It is for you to decide if this approach is fine for you or you wish to serialize the process. The serialization should be very simple with queues. All you need is to have a single one:
private lazy var fileProcessingQueue: DispatchQueue = DispatchQueue(label: "DownloadingFileData.main")
func sendFileWithURL(_ url: URL, completion: #escaping ((_ error: Error?) -> Void)) {
func finish(_ error: Error?) {
DispatchQueue.main.async {
completion(error)
}
}
fileProcessingQueue.async {
do {
let data: Data = try Data(contentsOf: url)
let base64String = data.base64EncodedString()
// TODO: send string to server and call the completion
finish(nil)
} catch {
finish(error)
}
}
}
Now one operation will finish before another one starts. But that may only apply for getting file data and conversion to base64 string. If uploading is then done on another thread (Which usually is) then you may still have multiple ongoing requests which may contain all of the data needed to upload.
How to check image nil when access to camera and press cancel on camera
I’m tested
First step
Press take photo button
access camera
Press cancel I’m get value on output panel
Check image take photo ==> Optional(UIImagePickerController: 0x107879a00)
Press save button photo on project
I’m getting value nil
Fatal error: Unexpectedly found nil while unwrapping an Optional value 2019-07-05 08:52:24.667938+0700 MyProject[2307:786209] Fatal error: Unexpectedly found nil while unwrapping an Optional value
I’m tested
the second step
Take photo
2.press use photo on the camera
Take photo press use photo on camera
Check image take photo ==> Optional(UIImagePickerController: 0x10286ca00)
I want to check case cancel on camera and go back press save button on the project
How to fix….this case
My code below
Take photo
#IBAction func takePhotoReturnOfSealButton(_ sender: UIButton) {
imagePickerStoreListReturnSealLock = UIImagePickerController()
imagePickerStoreListReturnSealLock.delegate = self
imagePickerStoreListReturnSealLock.sourceType = .camera
present(imagePickerStoreListReturnSealLock,animated: true, completion: nil)
print("Check image take photo ==> \(String(describing: imagePickerStoreListReturnSealLock))")
}//takePhotoReturnOfSealButton
Save photo
#IBAction func saveImageToDevice(_ sender: UIButton) {
//Save photo to device not success have nil value
if(imagePickerStoreListReturnSealLock == nil){
showAlert(title: "No Have Photo", message: "Please take photo")
print("Check image cannot save ==> \(String(describing: imagePickerStoreListReturnSealLock))")
}else{
// Save photo to device success
UIImageWriteToSavedPhotosAlbum(showImageTakePhotoReturnOfSeal.image!, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
print("Check image Save to device ==> \(String(describing: showImageTakePhotoReturnOfSeal))")
}
}//saveImageToDevice
Whenever you cancel the Image Picker a delegate method is been called -
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
//Perform your action when cancel has been pressed
}
This method is been called when Image is been picked -
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if picker == imagePicker {
//Save your image
imagePicker.dismiss(animated: true, completion: nil)
}
}
Just remember to define this above -
var imagePicker = UIImagePickerController()
and in viewDidLoad
imagePicker.delegate = self
When you click on cancel of image picker, following delegate method will be called:
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
and when you select an image, Following delegate method will be called:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let image : UIImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
picker.dismiss(animated: true, completion: nil)
UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)//to save image in saved photo album
}
Hope this helps
I'm building an APP with Swift 3 and backendless and i'm having trouble to upload an image from my APP (the user will take a pic from camera or from photoLibrary) to backendless.
The APP is saving the images to the photoLibrary :
func savePic() {
let imageData = UIImageJPEGRepresentation(itemPic.image!, 0.5);
let compressedJPEGImage = UIImage(data: imageData!);
UIImageWriteToSavedPhotosAlbum(compressedJPEGImage!, nil, nil, nil);
}
What is the taken image address?
And how do i upload it to backEndLess?
You should use Backendless.file.upload method.
For example:
func uploadImage(_ img:UIImage){
//convert UIImage to jpg Data with 0.5 compressionQuality (use 1 for full quality)
let jpg=UIImageJPEGRepresentation(img, 0.5);
//saving file name with current timestamp
let fileName="IMG_\(Date().timeIntervalSince1970).jpg";
//uploading image asynchronously
Backendless.sharedInstance().file.upload("imagesFolder/\(fileName)", content:jpg, response: {(res) in
//uploading image finished
}, error: {(e) in
//failed to upload
print("error code: \(e!.faultCode)");
})
}
If you need to upload it in PNG format use UIImagePNGRepresentation instead of IImageJPEGRepresentation
To upload an Image directly picked by user from saved photos album or taken by camera you can do it like this:
Use UINavigationControllerDelegate & UIImagePickerControllerDelegate
Specify UIImagePickerControllerSourceType to be used, such as: .photoLibrary, .camera or .savedPhotosAlbum
Upload the created UIImage when user didFinishPickingMediaWithInfo
Here an example:
class MyViewController : UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate{
var imgPicker = UIImagePickerController();
var backendless = Backendless.sharedInstance();
override func viewDidLoad() {
imgPicker.delegate = self;
}
#IBAction func imgFromCamera(_ btn:UIButton){
imgPicker.sourceType = .camera;//image picker with camera
show(imgPicker, sender: self);//open image picker
}
#IBAction func imgFromSavedPhotos(_ btn:UIButton){
imgPicker.sourceType = .savedPhotosAlbum;//image picker from saved photos
show(imgPicker, sender: self);//open image picker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let img = info[UIImagePickerControllerOriginalImage] as! UIImage;
uploadImage(img);
dismiss(animated: true, completion: nil);
}
func uploadImage(_ img:UIImage){
let jpg=UIImageJPEGRepresentation(img, 1);//full scale JPG
//let png=UIImagePNGRepresentation(img);//full scale PNG
let fileName="IMG_\(Date().timeIntervalSince1970).jpg";
//upload img
Backendless.sharedInstance().file.upload("imagesFolder/\(fileName)", content:jpg, response: {(res) in
//successfully uploaded
}, error: {(e) in
//failed to upload
})
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: false, completion: {
print("user didn't pick any image");
});
}
}
Also keep in mind that to use those you should declare: NSPhotoLibraryUsageDescription & NSCameraUsageDescription in you Info.plist otherwise it crash in iOS 10 as stated in linked reference.
Here an example:
<key>NSPhotoLibraryUsageDescription</key>
<string>Your explanation here</string>
<key>NSCameraUsageDescription</key>
<string>Your explanation here</string>
I have made a simple game using the Game template in Xcode, coded in swift. I created a shapeNode, and when it is touched, I would like this code to run:
if SLComposeViewController.isAvailableForServiceType(SLServiceTypeFacebook){
var controller = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
controller.setInitialText("Testing Posting to Facebook")
//self.presentViewController(controller, animated:true, completion:nil)
}
This code is run in the GameViewController.swift file, but gives this error. This error occurs on the commented line.
Could not cast value of type 'UIView' (0x379480d0) to 'SKView' (0x37227ad0).
Update: If you are targeting iOS 9 or above there are some small changes to make this work. You will need to add the correct URL schemes to your info.plist otherwise the check to see if the app is installed will not work.
NOTE: Its is a better idea to now use UIActivityController for sharing. This allows you to only use 1 button and you can share to all sorts of services.
http://useyourloaf.com/blog/querying-url-schemes-with-canopenurl/
To present a viewController in a SKScene you need to use the rootViewController
self.view?.window?.rootViewController?.presentViewController(...
I use a little helper for this using swift 2 protocol extensions, so you can use it anywhere you like in your app. The Facebook part looks like this, twitter is basically the same.
import SpriteKit
import Social
/// URLString
private struct URLString {
static let iTunesApp = URL(string: "Your iTunes app link")
static let facebookApp = URL(string: "Your Facebook app link")
static let facebookWeb = URL(string: "Your Facebook web link")
}
/// Text strings
private struct TextString {
static let shareSheetText = "Your share sheet text"
static let error = "Error"
static let enableSocial = "Please sign in to your account first"
static let settings = "Settings"
static let ok = "OK"
}
/// Social
protocol Social {}
extension Social where Self: SKScene {
/// Open facebook
func openFacebook() {
guard let facebookApp = URLString.facebookApp else { return }
guard let facebookWeb = URLString.facebookWeb else { return }
if UIApplication.shared.canOpenURL(facebookApp){
UIApplication.shared.openURL(facebookApp)
} else {
UIApplication.shared.openURL(facebookWeb)
}
}
/// Share to facebook
func shareToFacebook() {
guard SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) else {
showAlert()
return
}
guard let facebookSheet = SLComposeViewController(forServiceType: SLServiceTypeFacebook) else { return }
facebookSheet.completionHandler = { result in
switch result {
case .cancelled:
print("Facebook message cancelled")
break
case .done:
print("Facebook message complete")
break
}
}
let text = TextString.shareSheetText
//facebookSheet.setInitialText(text)
facebookSheet.setInitialText(String.localizedStringWithFormat(text, "add your score property")) // same as line above but with a score property
facebookSheet.addImage(Your UIImage)
facebookSheet.add(URLString.iTunesApp)
self.view?.window?.rootViewController?.present(facebookSheet, animated: true, completion: nil)
}
// MARK: - Private Methods
/// Show alert
private func showAlert() {
let alertController = UIAlertController(title: TextString.error, message: TextString.enableSocial, preferredStyle: .alert)
let okAction = UIAlertAction(title: TextString.ok, style: .cancel) { _ in }
alertController.addAction(okAction)
let settingsAction = UIAlertAction(title: TextString.settings, style: .default) { _ in
if let url = URL(string: UIApplicationOpenSettingsURLString) {
UIApplication.shared.openURL(url)
}
}
alertController.addAction(settingsAction)
self.view?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
To use the helper you simply go to the SKScene you need to call the methods and implement the protocol
class YourScene: SKScene, Social {....
Now when the Facebook node/button is pressed you can call the methods as if they are part of the scene itself.
openFacebook() // opens app or safari
shareToFacebook() // opens share sheet textField
all thanks to swift 2 and protocol extensions. The cool bit about this is say you want to use this helper in a regular UIKit app, than all you have to do is import UIKit instead of spriteKit
import UIKit
and change the protocol extension to this
extension Social where Self: UIViewController {....
Its quite nice and very flexible I think
Hope this helps.