Open Safari from my Today Extension (widget) within my app - ios

I have a Today Extension with a text field. I want to use the contents of the text field as a URL to open a browser within my app.
This is my TodayViewController.swift for my widget
import UIKit
import SafariServices
import NotificationCenter
// This extension to remove the white spaces from what pasteed
extension String {
func replace(string:String, replacement:String) -> String {
return self.replacingOccurrences(of: string, with: replacement,
options: NSString.CompareOptions.literal, range: nil)
}
func removeWhitespace() -> String {
return self.replace(string: " ", replacement: "")
}
}
class TodayViewController: UIViewController, NCWidgetProviding {
var clearNumber: String?
override func viewDidLoad() {
super.viewDidLoad()
}
func widgetPerformUpdate(completionHandler: (#escaping (NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(NCUpdateResult.newData)
}
#IBOutlet weak var textBox: UITextField!
#IBAction func clearNumber(_ sender: Any) {
if textBox.hasText == true {
textBox.text = ""
}else{
return
}
}
#IBAction func pasteNumber(_ sender: Any) {
if let myString = UIPasteboard.general.string {
let pasteNumber = myString.removeWhitespace()
textBox.insertText(pasteNumber)
}else{
return
}
}
#IBAction func goButton(_ sender: Any) {
let myAppUrl = URL(string: "main-screen:")!
extensionContext?.open(myAppUrl, completionHandler: { (success) in
if (!success) {
print("error: failed to open app from Today Extension")
}
})
}

You could use #Giuseppe_Lanza solution and parse url that you receive from Today Extension Widget. However, I would show an example where your url have a static components and looking for a path such as https:/www.apple.com/homepod or https:/www.apple.com/iphone based on user's input in the textField:
1- URL Scheme: myAppName
2- Add this to open your app with widget
#IBAction func goButton(_ sender: Any) {
openApp(widgetText: "\(textBox.text!)")
}
func openApp(widgetText:String) {
let str = "myAppName://https://www.apple.com/\(widgetText)"
let url = URL(string: str)!
if textBox.hasText == true {
extensionContext?.open(url, completionHandler: { (success) in
if (!success) {
print("error: 🧐")
}
})
}
}
3- AppDelegate
Define a variable and pass received url to webViewController, will parse url there.
open var receivedUrl:URL?
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool{
receivedUrl = url
//You need to alter this navigation to match your app requirement so that you get a reference to your previous view..
window?.rootViewController?.performSegue(withIdentifier: "toDeepLink", sender: nil)
}
Make sure to make add an identifier for this segue under the attributes
inspector as toDeepLink.
4- WebView & parsing url
Now you can get the receivedUrl like this
override func viewDidLoad() {
super.viewDidLoad()
let myAppDelegate = UIApplication.shared.delegate as! AppDelegate
print("receivedUrl \(myAppDelegate.receivedUrl!)")
//url Parsing & getting rid off urlScheme
let urlToLoad = URL(string: "\(myAppDelegate.receivedUrl!.host! + ":" + myAppDelegate.receivedUrl!.path)")!
print(urlToLoad)
let urlRequest = URLRequest(url: urlToLoad)
webView.load(urlRequest)
}
Else, you need to parse it in a proper way like dictionary to assign dynamic values to respective keys in your dictionary and hence to your url or append "?" to your urlToLoad just before you attempt to append url.query as I did in the webView controller.

You can do this by using deep linking.
First define a custom URL scheme
Once your app responds to the custom scheme my-app:// you can open your app from your todayViewController.
#IBAction func goButton(_ sender: Any) {
let myAppUrl = URL(string: "my-app://openurl/\(yourURL.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed))")!
extensionContext?.open(myAppUrl, completionHandler: { (success) in
if (!success) {
print("error: failed to open app from Today Extension")
}
})
}
In your app, just like described in the previous link you will have to implement in your app delegate
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
in this method, the url will be the one you created in your app extension. Meaning it will be my-app://openurl/{the url with percent escaping} You will have to parse this url, initialise the view controller that contains the webView and pass the url to be opened.

Related

Load WebView before transitioning to it

I had to update an app that i didn't touch for quite a while and now am facing an 'Fatal error: Unexpectedly found nil while unwrapping an Optional value' in DetailViewController::refreshWebView() when executing webView.loadRequest(myRequest) because webView is nil at this point in time.
I didn't change anything relating to how the DetailViewController is loaded and assigned to MasterViewController, so I am very confused as why this does not work anymore.
Was there something changed that i am not aware of? Or did I implement this whole thing incorrectly in the first place and it was coincidence that it worked?
import UIKit
protocol EventSelectionDelegate: class {
func eventSelected(_ newEvent: String)
}
class MasterViewController: UIViewController, UIWebViewDelegate {
var activityView: UIActivityIndicatorView!
weak var delegate: EventSelectionDelegate?
func detailChosen(_ detailUrlString: String) {
delegate?.eventSelected(detailUrlString)
activityView.startAnimating()
}
func transitionToDetail() {
if let detailViewController = self.delegate as? DetailViewController {
DispatchQueue.main.async {
self.activityView.stopAnimating()
self.splitViewController?.showDetailViewController(detailViewController, sender: nil)
}
}
}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
import UIKit
class DetailViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webView: UIWebView!
var animationWaitDelegate: MasterViewController!
var eventUrl: String! {
didSet {
self.refreshWebView()
}
}
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
}
func refreshWebView() {
let myURL = URL(string:eventUrl!)
let myRequest = URLRequest(url: myURL!)
webView.loadRequest(myRequest)
}
func webViewDidFinishLoad(_ webView: UIWebView) {
animationWaitDelegate.transitionToDetail()
}
}
extension DetailViewController: EventSelectionDelegate {
func eventSelected(_ newEvent: String) {
eventUrl = newEvent
}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
PS: I found a workaround in the meantime where I added a flag in DetailViewController that allows me to call refreshWebView in viewDidLoad if webView was nil the first time it was called.
First you have to update following code for null check, which will prevent crashing your app.
func refreshWebView() {
if webView != nil {
let myURL = URL(string:eventUrl!)
let myRequest = URLRequest(url: myURL!)
webView.loadRequest(myRequest)
}
}
After at transition add following code will fix your issue.
func transitionToDetail() {
if let detailViewController = self.delegate as? DetailViewController {
DispatchQueue.main.async {
self.activityView.stopAnimating()
detailViewController.loadViewIfNeeded() // Add this line of code
self.splitViewController?.showDetailViewController(detailViewController, sender: nil)
}
}
}
loadViewIfNeeded() method will load your controls before open screen if required.

UI elements are nil in share extension swift

I've been developing a share extension for my iOS app with a custom view controller. The custom view controller has an ImageView, a label and a textBox. I want to get the url from the webpage where the user tries to share with my app and set it as the text of the label. But when I try to get access to the label, it is nil. I've been trying this operation in diferente lifecycle methods, as viewDidLoad(), viewWillLoad() and finally I use viewDidAppear() but the result it's the same. The elements are properly connected in the storyboard to the Viewcontroller:
Here it's my ViewController code:
import UIKit
import Social
class ShareViewController: UIViewController {
#IBOutlet var ScreenCapture: UIImageView!
#IBOutlet var articleTitle: UITextField!
#IBOutlet var urlLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let item = extensionContext?.inputItems.first as? NSExtensionItem {
if let attachments = item.attachments {
for attachment: NSItemProvider in attachments {
if attachment.hasItemConformingToTypeIdentifier("public.url") {
attachment.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (url, error) in
if let shareURL = url as? URL {
self.urlLabel.text = shareURL.absoluteString
}
})
}
}
}
}
}
#IBAction func cancelShare(_ sender: Any) {
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
#IBAction func doneShare(_ sender: Any) {
//TODO: Insert into CoreData
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
}
I put a breakpoint in the self.urlLabel.text = shareURL.absoluteStringline and here it's the debugger information:
EDIT: Finally I solved my issue, I must call loadViewIfNeeded():
override func loadViewIfNeeded() {
super.loadViewIfNeeded()
let image = makeScreenShoot(withView: view)
if let item = extensionContext?.inputItems.first as? NSExtensionItem {
if let attachments = item.attachments {
for attachment: NSItemProvider in attachments {
if attachment.hasItemConformingToTypeIdentifier("public.url") {
attachment.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (url, error) in
if let shareURL = url as? URL {
self.urlLabel.text = shareURL.absoluteString
}
})
}
}
}
}
ScreenCapture.image = image
}

How to navigate to particular view controller of contained application from TodayViewController button click

I have set up app group, I am able to go to application but unable to call its delegate functions or view controller delegate function which make me in trouble to navigate it to a particular page of my application.how can i solve this issue? please help me out.
#IBAction func btnseconbutton3(_ sender: Any) {
var responder: UIResponder? = self as UIResponder
let selector = #selector(self.openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: "SecurityPPSwiftFinal://")!)
return
}
responder = responder?.next
}
}
func openURL(_ url: URL) {
return
}
//In appdelegate method
let defaults = UserDefaults(suiteName: "group.com.example.SecurityPPSwiftFinal") defaults?.synchronize()|
// In landing page i.e main view contoller will appear code
override func viewWillAppear(_ animated: Bool)
{
let defaults = UserDefaults(suiteName: "group.com.example.SecurityPPSwiftFinal") defaults?.synchronize()
}
UIApplication object is not available in App Extensions.
For APIs not available in App Extensions, refer to: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html#//apple_ref/doc/uid/TP40014214-CH2-SW2
To open the App from Today Extension use:
#IBAction func btnseconbutton3(_ sender: Any)
{
self.extensionContext?.open(URL(string: "SecurityPPSwiftFinal://")!, completionHandler: nil)
}
To handle button tap event in AppDelegate after the app is opened, use:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool
{
if url.scheme == "SecurityPPSwiftFinal"
{
//TODO: Write your code here
}
return true
}
For more on interaction between Today Extension and Host App, refer to: https://github.com/pgpt10/Today-Widget

How to add a hyperlink button on iOS Swift?

I'm trying to make a clickable link over a button.
I made this using some Internet help but it didn't work :
#IBAction func linkClicked(sender: AnyObject) {
openUrl("http://fr.envisite.net/t5exce")
}
func openUrl(url:String!) {
let targetURL=NSURL(fileURLWithPath: url)
let application=UIApplication.sharedApplication()
application.openURL(targetURL);
}
It doesn't do anything, no error, just the button doesn't get me on Safari (I use the iOS simulator)
You're creating a file URL with a web url string. Use the NSURL String constructor instead.
#IBAction func linkClicked(sender: AnyObject) {
openUrl("http://fr.envisite.net/t5exce")
}
func openUrl(urlStr:String!) {
if let url = NSURL(string:urlStr) {
UIApplication.sharedApplication().openURL(url)
}
}
Swift3
#IBAction func linkClicked(sender: Any) {
openUrl(urlStr: "http://fr.envisite.net/t5exce")
}
func openUrl(urlStr: String!) {
if let url = URL(string:urlStr), !url.absoluteString.isEmpty {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}

Retrieve Facebook user ID in Swift

I have a helper function that is grabbing the picture from a specific URL:
func getProfilePicture(fid: String) -> UIImage? {
if (fid != "") {
var imageURLString = "http://graph.facebook.com/" + fid + "/picture?type=large"
var imageURL = NSURL(string: imageURLString)
var imageData = NSData(contentsOfURL: imageURL!)
var image = UIImage(data: imageData!)
return image
}
return nil
}
I proceed to call getProfilePicture() with the user's Facebook ID, and store the output into a UIImageView. My question is, how can I find a Facebook user's ID? Do I request a connection through Facebook? It would be helpful if some code is provided here.
Thank you for your help.
If they are already successfully logged into your app:
FBSDKAccessToken.current().userID
What worked for me:
Use FBSDKLoginButtonDelegate
Implement func loginButtonWillLogin
Add notification to onProfileUpdated
In onProfileUpdated func proceed to get your Profile info
After onProfileUpdated is called, you can use your FBSDKProfile in any Controller
And now, some Swift 3 code...
import UIKit
import FBSDKLoginKit
class ViewController: UIViewController, FBSDKLoginButtonDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Facebook login integration
let logBtn = FBSDKLoginButton()
logBtn.center = self.view.center
self.view .addSubview(logBtn)
logBtn.delegate = self
}
func loginButtonWillLogin(_ loginButton: FBSDKLoginButton!) -> Bool {
print("will Log In")
FBSDKProfile.enableUpdates(onAccessTokenChange: true)
NotificationCenter.default.addObserver(self, selector: #selector(onProfileUpdated(notification:)), name:NSNotification.Name.FBSDKProfileDidChange, object: nil)
return true
}
func onProfileUpdated(notification: NSNotification)
{
print("profile updated")
print("\(FBSDKProfile.current().userID!)")
print("\(FBSDKProfile.current().firstName!)")
}
}

Resources