I recently started learning app development and almost every tutorial I can find is not in swift 3, so sometimes I run into confusion when it comes to the syntax for certain things that have been changed.
//Take the user to the game screen from the intro scene
let introLabel = childNode(withName: "introLabel")
if introLabel != nil{
let fadeOut = SKAction.fadeOut(withDuration: 1)
introLabel?.run(action: fadeOut, completion: () -> Void)
}
I have searched all around the internet and can't seem to find the missing thing I am supposed to put here (By completion: )
completion: () -> Void
It say that it will call a function have no parameter () and return type is Void
The function is like this
func handleCompletion() -> Void {
//Do something when complete
}
In your code, you can use like this.
introLabel?.run(action: fadeOut, completion: handleCompletion)
handleCompletion is my function above
Your can google for this by use closure swift or first class function swift keyword
I made a TurnBased game that was working perfectly with swift 2.
I could successfully change the code to swift 3.2 and correct all the errors about 400, but there is one issue with the game centre that I can not figure out how to fix it.
When through gamecenter viewcontroler I set the number of the participant to “2Player” and then push “Invite Friends” the number of Player that I get is 4 instead of 2!
the gamecenter send a text message to the next participant in which the number of participant is 4 too.
This of course affect the rest of the program a problem that I didn’t have in Swift 2.
This is part of my code:
func joinTurnBasedMatch()
{
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 4
request.defaultNumberOfPlayers = 2
let tbvc = GKTurnBasedMatchmakerViewController(matchRequest: request)
tbvc.turnBasedMatchmakerDelegate = self
present(tbvc, animated: true, completion: nil)
}
func turnBasedMatchmakerViewController(_ viewController: GKTurnBasedMatchmakerViewController, didFind match: GKTurnBasedMatch)
{
self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "To_ViewC_CG", sender: match)
print("*** Number OF Players ***")
print(match.participants!.count)
}
Do I need to add anything to my code or change anything in it?
The problem has been solved in the latest ios update.
Now it works perfectly.
I am trying to port my apps to iOS 10, including the visualization of a MPMediaPickerController by means of the following code:
#IBAction func handleBrowserTapped(_ sender: AnyObject){
let pickerController = MPMediaPickerController(mediaTypes: .music)
pickerController.prompt = NSLocalizedString("Add pieces to queue", comment:"");
pickerController.allowsPickingMultipleItems=true;
pickerController.delegate=MPMusicPlayerControllerSingleton.sharedController();
self.present(pickerController, animated:true, completion:{
MPMusicPlayerControllerSingleton.sharedController().storeQueue()
})
}
Yet all that appears on the screen is a full white screen with no back buttons or other, differently from the previous iOS versions. The block is called and so the picker's presentation seems to succeed. What could be the problem?
Add Key-value to Plist :
<key>NSAppleMusicUsageDescription</key>
<string>$(app Name) uses music</string>
The issue was fixed by the latest beta now asking for an authorisation to access the iTunes library.
I create a CSV file and try to send it by e-mail. Displays a window to send mail, but is not filled with the body of the email, and no attached file. Application hangs with this screen:
button "Cancel" does not work. After a few seconds in the console appears:
viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "The operation couldn’t be completed. (_UIViewServiceInterfaceErrorDomain error 3.)" UserInfo=0x7f8409f29b50 {Message=Service Connection Interrupted}
<MFMailComposeRemoteViewController: 0x7f8409c89470> timed out waiting for fence barrier from com.apple.MailCompositionService
There is my code:
func actionSheet(actionSheet: UIActionSheet!, clickedButtonAtIndex buttonIndex: Int) {
if buttonIndex == 0 {
println("Export!")
var csvString = NSMutableString()
csvString.appendString("Date;Time;Systolic;Diastolic;Pulse")
for tempValue in results { //result define outside this function
var tempDateTime = NSDate()
tempDateTime = tempValue.datePress
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
var tempDate = dateFormatter.stringFromDate(tempDateTime)
dateFormatter.dateFormat = "HH:mm:ss"
var tempTime = dateFormatter.stringFromDate(tempDateTime)
csvString.appendString("\n\(tempDate);\(tempTime);\(tempValue.sisPress);\(tempValue.diaPress);\(tempValue.hbPress)")
}
let fileManager = (NSFileManager.defaultManager())
let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if ((directorys) != nil) {
let directories:[String] = directorys!;
let dictionary = directories[0];
let plistfile = "bpmonitor.csv"
let plistpath = dictionary.stringByAppendingPathComponent(plistfile);
println("\(plistpath)")
csvString.writeToFile(plistpath, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
var testData: NSData = NSData(contentsOfFile: plistpath)
var myMail: MFMailComposeViewController = MFMailComposeViewController()
if(MFMailComposeViewController.canSendMail()){
myMail = MFMailComposeViewController()
myMail.mailComposeDelegate = self
// set the subject
myMail.setSubject("My report")
//Add some text to the message body
var sentfrom = "Mail sent from BPMonitor"
myMail.setMessageBody(sentfrom, isHTML: true)
myMail.addAttachmentData(testData, mimeType: "text/csv", fileName: "bpmonitor.csv")
//Display the view controller
self.presentViewController(myMail, animated: true, completion: nil)
}
else {
var alert = UIAlertController(title: "Alert", message: "Your device cannot send emails", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
else {
println("File system error!")
}
}
}
Trying instead to send mail using UIActivityViewController:
let fileURL: NSURL = NSURL(fileURLWithPath: plistpath)
let actViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
self.presentViewController(actViewController, animated: true, completion: nil)
See approximately the same screen to send e-mail, which after a while returning to the previous screen. In the console, now another error:
viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "The operation couldn’t be completed. (_UIViewServiceInterfaceErrorDomain error 3.)" UserInfo=0x7faab3296ad0 {Message=Service Connection Interrupted}
Errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo=0x7faab3005890 {NSLocalizedDescription=query cancelled}
<MFMailComposeRemoteViewController: 0x7faab3147dc0> timed out waiting for fence barrier from com.apple.MailCompositionService
There was something about PlugInKit.
Trying instead UIActivityViewController using UIDocumentInteractionController:
let docController = UIDocumentInteractionController(URL: fileURL)
docController.delegate = self
docController.presentPreviewAnimated(true)
...
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController!) -> UIViewController! {
return self
}
I see this screen with contents a CSV-file:
I press button export in top-right and see this screen:
where I choose MAIL and for several seconds I see:
Then returns to displaying the contents of the file! In the console the same messages as when using UIActivityViewController.
* * IMPORTANT - DO NOT USE THE SIMULATOR FOR THIS. * *
Even in 2016, the simulators very simply do not support sending mail from apps.
Indeed, the simulators simply do not have mail clients.
But! Do see the message at the bottom!
Henri has given the total answer. You MUST
-- allocate and initiate MFMailComposeViewController in an earlier stage, and
-- hold it in one static variable, and then,
-- whenever it's needed, get the static MFMailComposeViewController instance and use that.
AND you will almost certainly have to cycle the global MFMailComposeViewController after each use. It is not reliable to re-use the same one.
Have a global routine which releases and then re-initializes the singleton MFMailComposeViewController. Call to that global routine, each time, after you are finished with the mail composer.
Do it in any singleton. Don't forget that your app delegate is, of course, a singleton, so do it there...
#property (nonatomic, strong) MFMailComposeViewController *globalMailComposer;
-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
........
// part 3, our own setup
[self cycleTheGlobalMailComposer];
// needed due to the worst programming in the history of Apple
.........
}
and...
-(void)cycleTheGlobalMailComposer
{
// cycling GlobalMailComposer due to idiotic iOS issue
self.globalMailComposer = nil;
self.globalMailComposer = [[MFMailComposeViewController alloc] init];
}
Then to use the mail, something like this ...
-(void)helpEmail
{
// APP.globalMailComposer IS READY TO USE from app launch.
// recycle it AFTER OUR USE.
if ( [MFMailComposeViewController canSendMail] )
{
[APP.globalMailComposer setToRecipients:
[NSArray arrayWithObjects: emailAddressNSString, nil] ];
[APP.globalMailComposer setSubject:subject];
[APP.globalMailComposer setMessageBody:msg isHTML:NO];
APP.globalMailComposer.mailComposeDelegate = self;
[self presentViewController:APP.globalMailComposer
animated:YES completion:nil];
}
else
{
[UIAlertView ok:#"Unable to mail. No email on this device?"];
[APP cycleTheGlobalMailComposer];
}
}
-(void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error
{
[controller dismissViewControllerAnimated:YES completion:^
{ [APP cycleTheGlobalMailComposer]; }
];
}
{nb, fixed typo per Michael Salamone below.}
Have the following macro in your Prefix file for convenience
#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])
Also here's a "minor" problem which can cost you days: https://stackoverflow.com/a/17120065/294884
Just for 2016 FTR here's the basic swift code to send an email IN APP,
class YourClass:UIViewController, MFMailComposeViewControllerDelegate
{
func clickedMetrieArrow()
{
print("click arrow! v1")
let e = MFMailComposeViewController()
e.mailComposeDelegate = self
e.setToRecipients( ["help#smhk.com"] )
e.setSubject("Blah subject")
e.setMessageBody("Blah text", isHTML: false)
presentViewController(e, animated: true, completion: nil)
}
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
{
dismissViewControllerAnimated(true, completion: nil)
}
However! Note!
These days it is crappy to send an email "in app".
It's much better today to simply cut away to the email client.
Add to plist ...
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
</array>
and then code like
func pointlessMarketingEmailForClient()
{
let subject = "Some subject"
let body = "Plenty of <i>email</i> body."
let coded = "mailto:blah#blah.com?subject=\(subject)&body=\(body)".stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())
if let emailURL:NSURL = NSURL(string: coded!)
{
if UIApplication.sharedApplication().canOpenURL(emailURL)
{
UIApplication.sharedApplication().openURL(emailURL)
}
else
{
print("fail A")
}
}
else
{
print("fail B")
}
}
These days, that is much better than trying to email from "inside" the app.
Remember again the iOS simulators simply do not have email clients (nor can you send email using the composer within an app). You must test on a device.
It has nothing to do with Swift. It's an issue with the mail composer that's been around forever it seems. That thing is extremely picky, from failing with timeouts to sending delegate messages even when cancelled.
The workaround everyone uses is to create a global mail composer (for example in a singleton), and every single time reinitializing it when you need it. This ensures the mail composer is always around when the OS needs it, but also that it is free of any crap when you want to reuse it.
So create a strong (as global as possible) variable holding the mail composer and reset it every time you want to use it.
XCode 6 Simulator has problems managing Mailcomposer and other things.
Try testing the code with a real device. Likely it will work.
I have problems when running MailComposer from actionSheet button, also with real test. With IOS 7 worked fine, the same code in IOS 8 does not work. For me Apple must depurated the XCode 6. ( too many different simulated devices with Objective-C and Swift together ...)
Not sure if the recycling proposed in above solution is necessary or not. But you do need use proper parameters.
The delegate receives a MFMailComposeViewController* parameter. And you need to use that instead of self when dismissing the controller. I.e.
The delegate receives the (MFMailComposeViewController *) controller. And you need to use that instead of self when dismissing the MFMailComposeViewController controller. That is what you want to dismiss after all.
-(void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error
{
[controller dismissViewControllerAnimated:YES completion:^
{ [APP cycleTheGlobalMailComposer]; }
];
}
Create a property for the mail composer and instantiate it in view did load than call it when ever you need a mail composer.
#property (strong, nonatomic) MFMailComposeViewController *mailController;
self.mailController = [[MFMailComposeViewController alloc] init];
[self presentViewController:self.mailController animated:YES completion:^{}];
Hey this is solved with iOS 8.3 released 2 days ago.
A simple helper class for handling mail in Swift. Based on Joe Blow's answer.
import UIKit
import MessageUI
public class EmailManager : NSObject, MFMailComposeViewControllerDelegate
{
var mailComposeViewController: MFMailComposeViewController?
public override init()
{
mailComposeViewController = MFMailComposeViewController()
}
private func cycleMailComposer()
{
mailComposeViewController = nil
mailComposeViewController = MFMailComposeViewController()
}
public func sendMailTo(emailList:[String], subject:String, body:String, fromViewController:UIViewController)
{
if MFMailComposeViewController.canSendMail() {
mailComposeViewController!.setSubject(subject)
mailComposeViewController!.setMessageBody(body, isHTML: false)
mailComposeViewController!.setToRecipients(emailList)
mailComposeViewController?.mailComposeDelegate = self
fromViewController.presentViewController(mailComposeViewController!, animated: true, completion: nil)
}
else {
print("Could not open email app")
}
}
public func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
{
controller.dismissViewControllerAnimated(true) { () -> Void in
self.cycleMailComposer()
}
}
}
Place as instance variable in AppDelegate-class and call when needed.
So I'm testing out Apple's new Document Provider extension. I'm trying to open up a UIDocumentMenuViewController up, and that part is working. But when I try to click on one of the items it presents, it just cancels the action sheet. This happens even when I click on the iCloud item, which is there by default. My code for presenting the controller is as follows:
let type_data = kUTTypeData.__conversion()
let documentMenuViewController = UIDocumentMenuViewController(documentTypes: [type_data], inMode: UIDocumentPickerMode.Import)
navigationController.presentViewController(documentMenuViewController, animated: true, completion: nil)
Anyone know why this is happening?
You need to set the delegate on the UIDocumentMenuViewController. Then implement the -documentMenu:didPickDocumentPicker: delegate method. Then you can go ahead and present the documentPicker that was selected.
func documentMenu(documentMenu: UIDocumentMenuViewController!, didPickDocumentPicker documentPicker: UIDocumentPickerViewController!) {
documentPicker.delegate = self
self.presentViewController(documentPicker, animated: true, completion: nil)
}
There is a simple example of this here in Apple's guide.
Take a look at the NewBox Apple sample code and you'll see the step you are missing. Can't provide the actual answer here as iOS 8 is still under NDA.