I'm following this tutorial online https://makeapppie.com/2016/06/28/how-to-use-uiimagepickercontroller-for-a-camera-and-photo-library-in-swift-3-0/ (with a little bit of a twist). I'm trying to call my UIImagePickerController from a webview and I'm not sure how to change the code to get it to work properly. The difference is that I'm going to be receiving a call from javascript and then invoking the picker as a result instead of with a UIButton. Then I want to send the image back as a base64 string using my javascript interface.
Here is what I have so far.
import UIKit
import WebKit
class ViewController: UIViewController,
WKScriptMessageHandler,
UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
var webView: WKWebView?
let userContentController = WKUserContentController()
let picker = UIImagePickerController();
#IBAction func photoFromLibrary(_ sender: UIBarButtonItem) {
picker.allowsEditing = false
picker.sourceType = .photoLibrary
picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
present(picker, animated: true, completion: nil)
}
override func loadView() {
super.loadView()
let config = WKWebViewConfiguration()
config.userContentController = userContentController
self.webView = WKWebView(frame: self.view.bounds, configuration: config)
userContentController.add(self, name: "iOS")
let url = URL(string:"https://relate.lavishweb.com/account")
let request = URLRequest(url: url!)
_ = webView?.load(request)
self.view = self.webView
}
override func viewDidLoad() {
super.viewDidLoad()
picker.delegate = self
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
webView?.evaluateJavaScript("window.settings.setImageBase64FromiOS()") { (result, error) in
if error != nil {
print("Success")
} else {
print("Failure")
}
}
// now use the name and token as you see fit!
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject])
{
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2
// myImageView.contentMode = .scaleAspectFit //3
// myImageView.image = chosenImage //4
//I want to do additional stuff here and send back as a base64 String
dismiss(animated:true, completion: nil) //5
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
}
Hi I have the same problem an ein solved it, but it's a little dirty.
In viewDidLoad() you override the Delegate from WKWebView. The WKWebView use the Delegate, too. You need to save the Delegate local.
var oldDelegate: UIImagePickerControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
oldDelegate = picker.delegate // save the Delegate from WKWebView
picker.delegate = self
}
now you you can run your code in your delegate. In the end of your method imagePickerController() you have to invoke imagePickerController() from the old delegate.
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject]){
var myinfo = info
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2
// myImageView.contentMode = .scaleAspectFit //3
// myImageView.image = chosenImage //4
myinfo[UIImagePickerControllerOriginalImage] = chosenImage
myinfo[UIImagePickerControllerImageURL] = nil
oldDelegate?.imagePickerController!(picker, didFinishPickingMediaWithInfo: myinfo)
}
I set the URL nil, because the WKWebView load the image direct from drive if the URL is filled.
myinfo[UIImagePickerControllerImageURL] = nil
I hope this is helpful.
Okay I figured it out,
So I didn't know what IBAction was but basically that means that the function that I'm going to declare after it is something that is called by an Interface Builder Element such as a UIButton or something like that. After realizing this, I just changed the function to
func photoFromLibrary() {
picker.allowsEditing = false
picker.sourceType = .photoLibrary
picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
present(picker, animated: true, completion: nil)
}
then in my JavaScript interface I simply called the function when I received the call from JavasScript.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
webView?.evaluateJavaScript("window.settings.setImageBase64FromiOS()") { (result, error) in
if error != nil {
print("failure")
} else {
self.photoFromLibrary()
}
}
}
Stay tuned and I will post how to encode an image to a Base64 string while also shrinking it at load time.(Once I find out how to do that ofcourse)
EDIT: I figured out how to resize the image and convert to Base64 string very quickly.
I implemented this extension...
extension UIImage {
func resized(withPercentage percentage: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: size.width * percentage, height: size.height * percentage)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
}
func resized(toWidth width: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
and then I returned the string like so...
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject])
{
let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
let thumb = chosenImage.resized(toWidth: 72.0)
let imageData:NSData = UIImagePNGRepresentation(thumb!)! as NSData
let dataImage = imageData.base64EncodedString(options: .lineLength64Characters)
print(dataImage)
dismiss(animated:true, completion: nil) //5
}
Related
For some reason that I don't understand, the Action Extension Button (in Share menu) doesn't respond. Action extension, at this point, catches the URL from Safari (where it was launched from) to make some things after. As a layer between Web and extension there is JS file (maybe something wrong here, i just copied it)
ViewController:
class ActionViewController: UIViewController {
var SafariURL: NSURL!
override func viewDidLoad() {
super.viewDidLoad()
let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem
let itemProvider = extensionItem!.attachments?.first as? NSItemProvider
let propertyList = String(kUTTypePropertyList)
if itemProvider!.hasItemConformingToTypeIdentifier(propertyList) {
print("I'm here2")
itemProvider!.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in
let dictionary = item as? NSDictionary
OperationQueue.main.addOperation {
let results = dictionary![NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary
let urlString = results!["currentUrl"] as? String
self.SafariURL = NSURL(string: urlString!)
}
})
} else {
print("error")
}
}
#IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)
}
JS File:
var GetURL = function() {};
GetURL.prototype = {
run: function(arguments) {
arguments.completionFunction({ "currentUrl" : document.URL });
},
finalize: function(arguments) {
var message = arguments["statusMessage"];
if (message) {
alert(message);
}
}
};
var ExtensionPreprocessingJS = new GetURL;
Finally, you should change a content of override func viewDidLoad to
super.viewDidLoad()
if let inputItem = extensionContext?.inputItems.first as? NSExtensionItem {
if let itemProvider = inputItem.attachments?.first {
itemProvider.loadItem(forTypeIdentifier: kUTTypePropertyList as String) { [self] (dict, error) in
guard let itemDictionary = dict as? NSDictionary else { return }
guard let javaScriptValues = itemDictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else { return }
self.Pageurl = javaScriptValues["URL"] as? String ?? ""
JS is ok!
// ContentView.swift
// Shared
import Foundation
import SwiftUI
import JavaScriptCore
class cube {
var result: String
func do_js(text: String) -> String {
let jsSource = "var testFunct = function(message) { return \"Test Message: \" + message;}"
var context = JSContext()
context?.evaluateScript(jsSource)
let testFunction = context?.objectForKeyedSubscript("testFunct")
var result = testFunction?.call(withArguments: [text]).toString()
return result!
}
}
struct ContentView: View {
cube().do_js(text: "Hello world") // Starts forom here
var show_text = lol().result
var body: some View {
Text(show_text)
.font(.body)
.fontWeight(.black)
.foregroundColor(Color.red)
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
(Sorry, I'm beginner and come to swift even not from js, but from python! So it's incredibly new for me. But js more understandable for me from python.)
enter image description here
Here's the simplest version. In this version, it basically uses your original pattern where doJS returns a value. The disadvantage to this version is that doJS will get called every time the view renders.
class Cube {
func doJS(text: String) -> String? {
let jsSource = "var testFunct = function(message) { return \"Test Message: \" + message;}"
let context = JSContext()
context?.evaluateScript(jsSource)
let testFunction = context?.objectForKeyedSubscript("testFunct")
return testFunction?.call(withArguments: [text]).toString()
}
}
struct ContentView: View {
var body: some View {
Text(Cube().doJS(text: "Hello, world!") ?? "No result")
.font(.body)
.fontWeight(.black)
.foregroundColor(Color.red)
.padding()
}
}
And here's a slightly different version. In this version, Cube is an ObservableObject with a #Published value that stores the result. It will only get called once in onAppear.
class Cube : ObservableObject {
#Published var result : String?
func doJS(text: String) {
let jsSource = "var testFunct = function(message) { return \"Test Message: \" + message;}"
let context = JSContext()
context?.evaluateScript(jsSource)
let testFunction = context?.objectForKeyedSubscript("testFunct")
result = testFunction?.call(withArguments: [text]).toString()
}
}
struct ContentView: View {
#StateObject var cube = Cube()
var body: some View {
Text(cube.result ?? "No result")
.font(.body)
.fontWeight(.black)
.foregroundColor(Color.red)
.padding()
.onAppear {
cube.doJS(text: "Hello, world!")
}
}
}
So I have this function on my site:
function appQrHandlerSet(result) {
jQuery(function() {
jQuery('#readed_qr_url').val(result.url);
jQuery(this).getLayerForm('#qr_handler_layer');
});
}
From the iOS app, I have to call this function and pass a JSON to it, how can I achieve that?
I've been trying to make it work for 3 days now, but I gave up because something is not working right.
Thanks in advance!
According to your code, the parameter result should contain the property url. We suppose that the url contains the JSON data you want to pass.
Try the following 2 approaches:
// Approach 1:
func callJS() {
let json = "{ url:\"An url with json?\"}"
let scriptString = "let result=\(json); appQrHandlerSet(result);"
webView?.evaluateJavaScript(scriptString, completionHandler: { (object, error) in
})
}
// Approach 2:
func initWebViewWithJs() {
let config = WKWebViewConfiguration()
config.userContentController = WKUserContentController()
let json = "{ url:\"An url with json?\"}"
let scriptString = "let result=\(json); appQrHandlerSet(result);"
let script = WKUserScript(source: scriptString, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)
config.userContentController.addUserScript(script)
webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 320, height: 400), configuration: config)
}
I am launching a web-view from my swift app. From the web-view I want to call a swift function on a button click. I found the code sample here.
When I press the button, nothing happens. The URL I am loading in the web-view is https://test-swift-js.github.io
Here is the code :
import Cocoa
import WebKit
class ViewController : NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
var webView:WebView!
#IBAction func mybutton(_ sender: Any) {
let url = URL(string: "https://test-swift-js.github.io")
let request = URLRequest(url: url!)
webView = WebView(frame: NSRect(x: 0, y: 0, width: 1000, height: 1000))
webView.mainFrame.load(request)
self.view.addSubview(webView)
}
func webView(webView: WebView!, didClearWindowObject windowObject: WebScriptObject!, forFrame frame: WebFrame!) {
windowObject.setValue(self, forKey: "interOp")
}
//The name used to represent the Swift function name in Javascript.
class func webScriptNameForSelector(aSelector: Selector) -> String!
{
if aSelector == "callSwift:"
{
return "callSwift"
}
else
{
return nil
}
}
class func isSelectorExcludedFromWebScript(aSelector: Selector) -> Bool
{
return false
}
func callSwift(message:String)
{
var alert = NSAlert()
alert.messageText = message
alert.alertStyle = NSAlertStyle.informational
alert.beginSheetModal(for: self.view.window!, completionHandler: nil)
}
}
Maybe you forget to set the delegate.
hello i'm new with swift programming and have the following issue. I read about how i can get data from safari in my action extension. using code inside viewDidLoad. so far so good.
i build a tableView with a tutorial and it works fine.
know i would like to use data data i extracted form safari in to my tableView. The problem is that my tableView is loaded first and after that my data is pulled from safari.
how can i use the data directly in my TableView?
here my code. beware i am new at this and it is under construction. The tableVie is now filled with information as demonstrated in the tutorial.
import UIKit
import MobileCoreServices
var webDataArray : [String] = []
class ActionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var myTableView: UITableView!
var arrayOfWebData: [CustomCellContents] = [CustomCellContents]()
var effectStannd = false
override func viewDidLoad() {
super.viewDidLoad()
for item: AnyObject in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: AnyObject in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as! String) {
itemProvider.loadItemForTypeIdentifier(kUTTypePropertyList as! String, options: nil, completionHandler: {(list, error) in
if let results = list as? NSDictionary {
NSOperationQueue.mainQueue().addOperationWithBlock {
var webData = results.description
webDataArray = split(webData) {$0 == ","}
var testArray = split(webDataArray[0]) {$0 == " "}
webDataArray.removeAtIndex(0)
webDataArray.append(testArray[7])
testArray = split(webDataArray[13]) {$0 == " "}
webDataArray.removeAtIndex(13)
webDataArray.append(testArray[1])
//println(webDataArray)
println(webDataArray.count)
}
}
})
}
}
}
println(webDataArray.count)
self.setUpWebData()
self.addEffect()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequestReturningItems(self.extensionContext!.inputItems, completionHandler: nil)
}
func setUpWebData()
{
var webData1 = CustomCellContents(fileName: "Torrent", fileKind: "img1.jpeg")
var webData2 = CustomCellContents(fileName: "Magnet", fileKind: "img2.jpeg")
var webData3 = CustomCellContents(fileName: "Exe", fileKind: "img1.jpeg")
var webData4 = CustomCellContents(fileName: "DMG", fileKind: "img2.jpeg")
arrayOfWebData.append(webData1)
arrayOfWebData.append(webData2)
arrayOfWebData.append(webData3)
arrayOfWebData.append(webData4)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return arrayOfWebData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomCell
let CustomCellContents = arrayOfWebData[indexPath.row]
cell.setCell(CustomCellContents.fileName, imageName: CustomCellContents.fileKind)
return cell
}
/////// Custom swip from Right
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
var shareAction = UITableViewRowAction(style: .Normal, title: "Download") { (action: UITableViewRowAction!, indexPath: NSIndexPath!) -> Void in
let firstActivityItem = self.arrayOfWebData[indexPath.row]
let activityViewControler = UIActivityViewController(activityItems: [firstActivityItem], applicationActivities: nil)
self.presentViewController(activityViewControler, animated: true, completion: nil)
}
shareAction.backgroundColor = UIColor.blueColor()
return [shareAction]
}
//// de volgende functie zorgt ervoor dat de rij automatisch word gedeselecteerd
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
self.myTableView.deselectRowAtIndexPath(indexPath, animated: true)
}
func addEffect(){
if effectStannd{
var effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
var effectView = UIVisualEffectView(effect: effect)
effectView.frame = CGRectMake(0, 0, 320, 600)
view.addSubview(effectView)
effectStannd = false
}
}
}
I found the solution in refreshing my tableview after loading. It works fine like this.
func didRefreshList(){
var tableView = myTableView
self.tableData = webDataArray3
tableView.reloadData()
self.refreshControl.endRefreshing()
}