Add variable to URL in swift - ios

I am currently developing an app in Xcode with Swift. The general premise of the app is that when the app first loads, the user will be asked to enter a 'mobile linking code' this will essentially be added onto the end of the url as a variable which will serve as their login to the site, the rest of the authentication is done server side. Then when the user logs into the app each time after that, this variable will be auto applied to the URL so that essentially they are always auto logged in.
I have my code setup for the app and the UIAlertController loads with a text field, I am struggling on how to find out how to append the 'mobile linking code' (which the user will type into the text field) to the end of the URL on first load and then also how to append this to the URL that loads each time after that.
The code I have is as follows
At the top of my WebViewController.swift
var webviewurl = "https://mywebsite.co.uk/loginarea.php?link=" (I need to append the mobile link code to the end of that url)
Further down in my code I have my first run dialog, in which I have added a UIAlertController. This will be ran on the very first time opening the app only in which the user will input their 'mobile link code', upon clicking Submit, the webview should be redirected to the url with the data in the text field appended to the end.
if activatefirstrundialog == "true" {
if !user.bool(forKey: "firstrun")
{
user.set("1", forKey: "firstrun")
user.synchronize()
webView.stopLoading()
let ac = UIAlertController(title: "Enter Mobile Link Code", message: "Enter the mobile link code found in your Client Area", preferredStyle: .alert)
ac.addTextField()
let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in
let answer = ac.textFields![0]
// do something interesting with "answer" here
let url = URL(string: "https://mywebsite.co.uk/loginarea.php?link=")!
self.webView.load(URLRequest(url: url + answer))
}
ac.addAction(submitAction)
present(ac, animated: true)
}
}
}
I would be eternally grateful if someone could help me with this.
TIA

To use the mobileLink between multiple app sessions, you need to save it somewhere after it is entered for the first time by the user.
Let's say we save it in UserDefaults. You can then fetch its value accordingly like so,
if !user.bool(forKey: "firstrun") {
//your code...
let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in
if let answer = ac.textFields.first?.text {
UserDefaults.standard.set(answer, forKey: "mobileLink")
let url = URL(string: "https://mywebsite.co.uk/loginarea.php?link=\(answer)")!
self.webView.load(URLRequest(url: url))
}
}
//your code...
} else if let mobileLink = UserDefaults.standard.string(forKey: "mobileLink") {
let url = URL(string: "https://mywebsite.co.uk/loginarea.php?link=\(mobileLink)")!
self.webView.load(URLRequest(url: url))
}

Did you mean something like this?
let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in
let answer = ac.textFields![0]
// do something interesting with "answer" here
if let answerText = answer.text, answerText.count > 0 {
if let percentageEncodedString = "https://mywebsite.co.uk/loginarea.php?link=\(answerText)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
if let url = URL(string:percentageEncodedString) {
self.webView.load(URLRequest(url: url))
} else {
//Handle error here, url is not valid
}
} else {
//Handle error here, url cannot be encoded
}
} else {
//Handle error here, text field's text is nil or text is empty
}
}

You should store this mobile linking code somewhere safe. you can use keychain to store it.
To answer your question, you can concatenate the string and form a URL Like this:
let url = URL(string: "https://mywebsite.co.uk/loginarea.php?link=\(answer)")!
Here answer is a variable.

Related

Alamofire Authentication Using Old Credentials

I am using Alamofire to sign into my app. The username and password come from text fields. I can sign in fine but if I sign out and return to the log in screen each subsequent log in attempt uses the original credentials. For example, if I enter 'test#test.com' and 'password', I can get in but then if I sign out and enter 'test2#test.com' and 'test2password', it uses the first credentials for 'test#test.com'. Also, if I enter incorrect credentials, it will always say they incorrect even after I enter the correct credentials. The only way to get it to accept a different set of credentials is to force close the app and reopen it. The other part of this is that each subsequent call to other endpoints after the sign in requires user credentials. That all works fine once I sign in, but when I fix the log in issue by using an authorization header and not the Alamofire authenticate method, my subsequent calls don't work.
Here is how I'm trying to sign in so all of my subsequent calls work but this causes the first set of credentials to be used every time until I force close the app.
Alamofire.request("https://example.com/signin/", method: .get).authenticate(user: userName, password: password).responseJSON { response in
if response.result.value != nil {
let results = response.result.value as? [[String: AnyObject]]
if results!.count > 0 {
if let dictionary = results?[0] {
if let userEmail = dictionary["email"] {
print("Signed in with: \(userEmail)")
sharedUser.userJSON = JSON(response.result.value!)
sharedUser.userEmail = self.usernameField.text!
sharedUser.userPassword = self.passwordField.text!
DispatchQueue.main.async {
self.performSegue(withIdentifier: "signInSegue", sender: nil)
}
}
}
} else {
DispatchQueue.main.async {
let failedSignInAlert = UIAlertController(title: "Invalid Email or Password", message: "The information you entered is incorrect. Please verify you have the correct information and try again.", preferredStyle: .alert)
let failedAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
let cookieStorage = HTTPCookieStorage.shared
for cookie in cookieStorage.cookies! {
cookieStorage.deleteCookie(cookie)
}
let urlCache = URLCache.shared
urlCache.removeAllCachedResponses()
self.dismiss(animated: true, completion: nil)
})
failedSignInAlert.addAction(failedAction)
self.present(failedSignInAlert, animated: true, completion: nil)
}
}
} else {
print("SIGN IN FAILED!")
}
DispatchQueue.main.async {
self.loadingIndicator.stopAnimating()
self.signInButton.isEnabled = true
}
}
By default, .authenticate uses a URLCredential.Storage value of .forSession, which means that the credential will be used for all auth challenges in a session automatically, and Alamofire doesn't get a chance to provide it's new URLCredential. Passing the value of .none may fix your issue.
.authenticate(user: userName, password: password, persistence: .none)

Swift 3 iMessage Extension doesn't open URL

I am creating an iOS Application iMessage Extension.
According to Example by Apple, I creating a message according to provided logic
guard let url: URL = URL(string: "http://www.google.com") else { return }
let message = composeMessage(url: url)
activeConversation?.insert(message, completionHandler: { [weak self] (error: Error?) in
guard let error = error else { return }
self?.presentAlert(error: error)
})
also
private func composeMessage(url: URL) -> MSMessage {
let layout = MSMessageTemplateLayout()
layout.caption = "caption"
layout.subcaption = "subcaption"
layout.trailingSubcaption = "trailing subcaption"
let message = MSMessage()
message.url = url
message.layout = layout
return message
}
and
private func presentAlert(error: Error) {
let alertController: UIAlertController = UIAlertController(
title: "Error",
message: error.localizedDescription,
preferredStyle: .alert
)
let cancelAction: UIAlertAction = UIAlertAction(
title: "OK",
style: .cancel,
handler: nil
)
alertController.addAction(cancelAction)
present(
alertController,
animated: true,
completion: nil
)
}
As far as I understand, after message is sent, on a click, Safari browser should be opened.
When I click on a sent message, MessageViewController screen takes place in whole screen, without opening safari or another app.
Where is the problem? How can I achieve desired functionality?
Here is the code I use to open a URL from a iMessage extension. It is currently working to open the Music app in the WATUU iMessage application. For instance with the URL
"https://itunes.apple.com/us/album/as%C3%AD/1154300311?i=1154300401&uo=4&app=music"
This functionality currently works in iOS 10, 11 and 12
func openInMessagingURL(urlString: String){
if let url = NSURL(string:urlString){
let context = NSExtensionContext()
context.open(url, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil){
if responder?.responds(to: Selector("openURL:")) == true{
responder?.perform(Selector("openURL:"), with: url)
}
responder = responder!.next
}
}
}
UPDATE FOR SWIFT 4
func openInMessagingURL(urlString: String){
if let url = URL(string:urlString){
let context = NSExtensionContext()
context.open(url, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil){
if responder?.responds(to: #selector(UIApplication.open(_:options:completionHandler:))) == true{
responder?.perform(#selector(UIApplication.open(_:options:completionHandler:)), with: url)
}
responder = responder!.next
}
}
}
I think safari Browser only opens for macOS. This worked for me:
override func didSelectMessage(message: MSMessage, conversation: MSConversation) {
if let message = conversation.selectedMessage {
// message selected
// Eg. open your app:
let url = // your apps url
self.extensionContext?.openURL(url, completionHandler: { (success: Bool) in
})
}
}
Using the technique shown by Julio Bailon
Fixed for Swift 4 and that openURL has been deprecated.
Note that the extensionContext?.openURL technique does not work from an iMessage extension - it only opens your current app.
I have posted a full sample app showing the technique on GitHub with the relevant snippet here:
let handler = { (success:Bool) -> () in
if success {
os_log("Finished opening URL")
} else {
os_log("Failed to open URL")
}
}
let openSel = #selector(UIApplication.open(_:options:completionHandler:))
while (responder != nil){
if responder?.responds(to: openSel ) == true{
// cannot package up multiple args to openSel so we explicitly call it on the iMessage application instance
// found by iterating up the chain
(responder as? UIApplication)?.open(url, completionHandler:handler) // perform(openSel, with: url)
return
}
responder = responder!.next
}
It seems it is not possible to open an app from a Message Extension, except the companion app contained in the Workspace. We have tried to open Safari from our Message Extension, it did not work, this limitation seems by design.
You could try other scenari to solve your problem :
Webview in Expanded Message Extension
You could have a Webview in your Message Extension, and when you click
on a message, you could open the Expanded mode and open you Url in the
Webview.
The user won't be in Safari, but the page will be embedded in your Message Extension.
Open the Url in the Companion App
On a click on the message, you could open your Companion app (through
the Url Scheme with MyApp://?myParam=myValue) with a special parameter
; the Companion app should react to this parameter and could redirect
to Safari through OpenUrl.
In this case, you'll several redirects before the WebPage, but it should allow to go back to the conversation.
We have also found that we could instance a SKStoreProductViewController in a Message Extension, if you want to open the Apple Store right in Messages and let the user buy items.
If you only need to insert a link, then you should use activeConversation.insertText and insert the link. Touching the message will open Safari.
openURL in didSelectMessage:conversation: by using extensionContext
handle the URL scheme in your host AppDelegate

PerformSegueWithIdentifier not working Swift

I have a tableView on my page and when I press a specific row, my app is supposed to show another page using segue. However when i click on the appropriate row it freezes, and then when I click a different row the segue finally shows up, which is odd. The code used to work for me but for some reason stopped, and I can't identify the issue because the code is identical (at least as far as I can see). Here is a code snippet :
func restClient(client: DBRestClient!, loadedFile destPath: String!, contentType: String!, metadata: DBMetadata!){
let title = "This format is incorrect"
let message = "You can only download file that is in .txt format"
let okText = "OK"
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let okayButton = UIAlertAction(title: okText, style: UIAlertActionStyle.Cancel, handler: nil)
alert.addAction(okayButton)
if contentType.rangeOfString("text") != nil{
print("this is text")
self.performSegueWithIdentifier("segue", sender: nil)
}
else{
print("this is an error")
presentViewController(alert, animated: true, completion: nil)
return;
}
let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! NSString
print("The file \(metadata.filename) was downloaded. Content type: \(contentType). The path to it is : \(documentsDirectoryPath)" )
progressBar.hidden = true
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let localFilePath = documentsURL.URLByAppendingPathComponent("Documents")
let checkValidation = NSFileManager.defaultManager()
if (checkValidation.fileExistsAtPath(localFilePath.path!))
{
print("FILE AVAILABLE");
}
else
{
print("FILE NOT AVAILABLE");
}
return
}
didDeselectRowAtIndexPath code :
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath){
let selectedFile: DBMetadata = dropboxMetadata.contents[indexPath.row] as! DBMetadata
let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let localFilePath = (documentsDirectoryPath as NSString).stringByAppendingPathComponent(selectedFile.filename)
print("The file to download is at: \(selectedFile.path)")
print("The documents directory path to download to is : \(documentsDirectoryPath)")
print("The local file should be: \(localFilePath)")
showProgressBar()
dbRestClient.loadFile(selectedFile.path, intoPath: localFilePath as String)
}
The picture below illustrates when I click on the file.txt row. As you can see it just stays grey and nothing happens, but after that, if I click on another file, say enumrec.pdf, it will show the appropriate page. Would be happy if anyone could point out what i am doing wrong here.
You should use didSelectRowAtIndexPath instead of didDeselectRowAtIndexPath. The latest is often propose first when using autocompletion, it's easy to make the mistake.
It looks like you are performing your seque after your data call which is on a different thread (so your app can carry on when your data call is running). When you change UI you have to run it on the main thread otherwise you can have issues like this. Just wrap your performSegue code in this:
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier("segue", sender: nil)
});
You can read about threads/backgrounds tasks etc here:
https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

How can I create a 'share to Facebook button' in a SpriteKit game using swift?

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.

App crashes after login or signup, but works after I swipe to quit and reopen

The title is kind of lengthy and may be a bit convoluted, but I'll try to break it down. My app is built with Swift and uses Parse as the backend. Upon a successful signup or login, the app opens to the main screen and everything is working. I go to profile, and all of the users signup information is there. I go to the camera, and can take a photo, but when I try to post the photo and send it to Parse, the app crashes. If I exit the app and swipe quit it, then reopen it with the cached user already logged in, everything works perfectly. I can take photos, post them, view them, and there are no issues. My problem only appears when I'm signing up for the first time or logging in. I would post code, but I have no idea what to post, if anyone can give me some assistance I'd appreciate it. Thanks!
EDIT
My Apologies for the ambiguity of my first question....I finally have an error message that states: 'NSInvalidArgumentException', reason: 'Can't use nil for keys or values on PFObject. Use NSNull for values.'....not sure what to make of it, but if someone could help I'd be very grateful.
Code
Here is the code of the method that I believe is causing the crash.
#IBAction func postPhoto(sender: AnyObject)
{
var fileData:NSData
var fileName:NSString
var fileType:NSString
if image != nil
{
fileData = UIImageJPEGRepresentation((image), 0.7)
fileName = "image.png"
fileType = "image"
}
else
{
fileData = NSData.dataWithContentsOfMappedFile(videoFilePath) as NSData
fileName = "video.mov"
fileType = "video"
}
let file = PFFile(name: fileName, data: fileData)
var content = PFObject(className: "Content")
content["sender"] = self.currentUser
content["senderObjectId"] = self.currentUser?.objectId
content["senderUsername"] = self.currentUser?["displayUsername"]
content["senderProfilePic"] = self.currentUser?["profilePic"]
content["file"] = file
content["recipients"] = self.photoRecipients
content["caption"] = self.photoCaption.text
content.saveInBackgroundWithBlock { (success:Bool!, error:NSError!) -> Void in
if success != nil
{
self.dismissViewControllerAnimated(false, completion: nil)
self.reset()
}
else
{
var alert:UIAlertController = UIAlertController(title: "Error", message: "There is a poor network connection, please try again", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Close", style: .Cancel, handler: nil))
}
}
}
Answer
The problem was in my viewDidLoad. I was checking for currentUser and then setting photoRecipients based off of that. There is no currentUser when the view controller is initially called, the login screen is shown. Because of that photoRecipients is never set, and when the main view controller shows up again, viewDidLoad has already been called and photoRecipients is still set to nil.
override func viewDidLoad()
{
super.viewDidLoad()
currentUser = PFUser.currentUser()
if currentUser != nil
{
var query = PFUser.query()
query.orderByAscending("username")
query.findObjectsInBackgroundWithBlock({ (NSArray objects, NSError error) -> Void in
if error == nil
{
for user in objects
{
self.contentRecipients.append(user.objectId)
}
}
})
currentUserObjectId = currentUser?.objectId
currentUserDisplayUsername = currentUser?["displayUsername"] as? NSString
}
else
{
self.performSegueWithIdentifier("showLogin", sender: self)
}
}
The issue with your code is that self.user, self.photoRecipients, and/or self.photoCaption.text is/are nil. You need to make sure none are nil and it should work.

Resources