I want to implement my custom MenuController once user selects a text. I am using the below code to do that, I subclassed WKWebview and implemented below
override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: WKWebViewConfiguration())
enableCustomMenu()
}
func enableCustomMenu() {
let menuController = UIMenuController.shared
let testmenu = UIMenuItem(title: "Test", action: #selector(test))
menuController.menuItems = [testmenu]
}
func test(){
var text = ""
self.evaluateJavaScript("document.getSelection().toString()") { (data, error) in
text = data as! String
}
print(text)
}
override func becomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(test):
return true
default:
return false
}
}
This used to work fine for UIWebview, but in WKWebview, in the canPerformAction we are no longer getting copy, lookup and share actions so these guys are not getting removed.
I had this problem too, I found that you can customize your WKwebview by overriding the function canPerformAction
here is an article about it .
It worked for me!
Hope that help you.
Related
After ios update to 13.4.1 to able to detect tap gesture in WKWebView, my code as follow:
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(viewTap) )
tapGesture.delegate = self
webview.addGestureRecognizer(tapGesture)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#objc func viewTap() {
print("tap on viewTap...")
}
I've created a minimum reproducible example of this problem I'm facing.
First, I created a WKWebView housed in a UIViewRepresentable to be used with SwiftUI. Then, I set up a WKUserContentController and a WKWebViewConfiguration so the WKWebView can send messages to native code. In this case, I have a <textarea> that sends over its value on input.
The value sent over through WebKit is assigned to a #State variable which causes the view to update. Unfortunately, the WKWebView is deselected whenever the view updates. How can I work around this? The WKWebView needs to stay selected until the user deliberately chooses to hide the keyboard.
This is a minimal example of what's happening:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let html: String
let configuration: WKWebViewConfiguration
func makeUIView(context: Context) -> WKWebView {
.init(frame: .zero, configuration: configuration)
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.loadHTMLString(html, baseURL: nil)
}
}
struct ContentView: View {
final class Coordinator: NSObject, WKScriptMessageHandler {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let text = message.body as? String else { return }
self.text = text
}
}
#State var text = ""
var body: some View {
WebView(
html: """
<!DOCTYPE html>
<html>
<body>
<textarea>\(text)</textarea>
<script>
const textarea = document.querySelector('textarea')
textarea.oninput = () =>
webkit.messageHandlers.main.postMessage(textarea.value)
</script>
</body>
</html>
""",
configuration: {
let userContentController = WKUserContentController()
userContentController.add(Coordinator(text: $text), name: "main")
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
return configuration
}()
)
}
}
I would recommend (as I see the simplest in this scenario) to change handling event to
textarea.onblur = () =>
webkit.messageHandlers.main.postMessage(textarea.value)
otherwise a complex refactoring is needed to keep away WK* entities from SwiftUI representable wrapper, or changing model to avoid using #State or anything resulting in view rebuild, or both of those.
I am trying to save a imageview as a image to binary data in core data. My code is not working. It has a compile error. In View controller it is not regisitering cdHandler. All i want to do is save the the imaveview as binary data in a core data model.I have 2 classes a app delegate and a view controller.
CLASS VIEW CONTROLLER
import UIKit
import CoreData
class ViewController: UIViewController {
var canVasView = UIImageView()
#objc func hhh() {
let photo = self.canVasView.image
let data = photo!.pngData()
if cdHandler.saveObject(pic: data!){
}
}
}
APP DELEGATE
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
class cdHandler: NSObject {
private class func getContext() -> NSManagedObjectContext {
let appdeleagetzz = UIApplication.shared.delegate as! AppDelegate
return appdeleagetzz.persistentContainer.viewContext
}
class func saveObject(pic: Data, userName: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "User", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(pic, forKey:"pic")
managedObject.setValue(userName, forKey:"userName")
do {
try context.save()
return true
} catch {
return false
}
}
class func deletObject(user: User) -> Bool {
let context = getContext()
context.delete(user)
do {
try context.save()
return true
} catch {
return false
}
}
class func fetchObject() -> [User]? {
do {
let context = getContext()
return try context.fetch(User.fetchRequest())
} catch {
return [User]()
}
}
}
}
The error message, *Value of type 'AppDelegate' has no member named 'persistentContainer', explains the problem. Indeed, when I look at the code for your AppDelegate class, I can confirm that it has no member named 'persistentContainer'. (If I am reading it correctly, the last two lines in the file are closing curly brackets. The first one closes your cdHandler nested class, and the second one closes your AppDelegate class.)
Do the following exercise. In Xcode, click in the menu: File > New Project and select iOS, Application and Single View App. Name your new project Junk. Switch on the Core Data checkbox. Click button Create. After it is done, look at the AppDelegate.swift which Xcode created, and in the AppDelegate class, you see it contains 8 functions (func). The 7th one is lazy var persistentContainer. Aha! The compiler is telling you that you probably should not have deleted those 8 functions, persistentContainer in particular.
You should copy that persistentContainer func from that Junk project into your AppDelegate class in your real project. Or, to head off future trouble, consider copying most of the other 7 funcs also. As you can see, most of them don't do anything except provide comments with explanations that are useful for beginners. After you are done copying, close the Junk project. (I overwrite my Junk project with a new Junk project several times in a typical week, especially when answering StackOverflow questions.)
That should fix this particular error and answer this question. Onward to the next issue. :)
Response to comment that you still get the error with cdHandler
Having nothing else to go on, I presume that the error that you are referring to is the compiler error still in your screenshot. In other words, you are saying that adding the persistentContainer definition did not make it any better.
Well, it works for me. Please replace all of the code in your AppDelegate.swift class with the following, build and run it…
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppDelegate.cdHandler.testGetContext()
return true
}
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "Junk")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
class cdHandler: NSObject {
private class func getContext() -> NSManagedObjectContext {
let appdeleagetzz = UIApplication.shared.delegate as! AppDelegate
return appdeleagetzz.persistentContainer.viewContext
}
class func testGetContext() {
let context = getContext()
print("getContext() succeeded, got \(context)")
}
class func saveObject(pic: Data, userName: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "User", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(pic, forKey:"pic")
managedObject.setValue(userName, forKey:"userName")
do {
try context.save()
return true
} catch {
return false
}
}
class func deletObject(user: NSManagedObject) -> Bool {
let context = getContext()
context.delete(user)
do {
try context.save()
return true
} catch {
return false
}
}
}
}
You see that compiles with no errors. Also, it runs and the AppDelegate.cdhandler.getContext() method works. As you can see, in AppDelegate.application(application:didFinishLaunchingWithOptions:), I have added a call to a new method which I defined later,AppDelegate.cdHandler.testGetContext()`. It works perfectly.
Are you getting a different error now? If so, you need to specify whether it is a Build or Run error. In either case, copy and paste the text of the error into your question, and tell us where it occurs.
I have created a table view cell in story board and i created a cocoa touch class for it.In that it will have one button, so here i want to navigate to another view controller on clicking on the button programmatically.
This my code
#IBOutlet weak var findOutButton: UIButton!
override func awakeFromNib()
{
super.awakeFromNib()
findOutButton.addTarget(self, action: Selector(("action:")), for: UIControlEvents.touchUpInside)
}
func action(sender: UIButton) {
let vc5 = self.storyboard.instantiateViewController(withIdentifier: "DescriptionViewController") as? DescriptionViewController
self.navigationController?.pushViewController(vc5!, animated: true)
}
Here its showing error in this line
let vc5 = self.storyboard.instantiateViewController(withIdentifier: "DescriptionViewController") as? DescriptionViewController
`
like "value of type 'TableViewCell' has no member 'storyboard'.
Thanks in advance.Help me to clear out the error.
You cannot access self.storyboard from your UITableCell. You should navigate from the main ViewController, that contains your UITableView. And for this you need to use delegates. At top of your UITableCell class add this :-
protocol CustomTableDelegate {
func DelegateButtonPressed()
}
class YourClass : UITableViewCell{
var delegate : CustomTableDelegate? = nil
override func awakeFromNib()
{
super.awakeFromNib()
findOutButton.addTarget(self, action: Selector(("action:")), for: UIControlEvents.touchUpInside)
}
func action(sender: UIButton) {
self.delegate?.DelegateButtonPressed()
}
}
And In your main View Controller in which you have your UITableView, in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourClass", for: indexPath) as! YourClass
cell.delegate = self
return cell
}
And also add delegate as :-
class YOURVIEWCONTROLLER:UIViewController, CustomTableDelegate{
func DelegateButtonPressed(){
let vc5 = self.storyboard.instantiateViewController(withIdentifier: "DescriptionViewController") as? DescriptionViewController
self.navigationController?.pushViewController(vc5!, animated: true)
}
}
Let me know if you find any difficulty using this.
Create Completion Block For getting Button Action on View Controller Class
class YourCellClass : UITableViewCell{
var completionBlock : ((_ sender : UIButon)->())?
override func awakeFromNib()
{
super.awakeFromNib()
findOutButton.addTarget(self, action: Selector(("action:")), for: UIControlEvents.touchUpInside)
}
func action(sender: UIButton) {
completionBlock?(sender)
}
}
In View Controller Execute the Block
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellClass", for: indexPath) as! YourCellClass
cell.completionBlock = { (sender) -> Void in
/* Here sender is button refrence so you can modify property of the button also */
let vc5 = self.storyboard.instantiateViewController(withIdentifier: "DescriptionViewController") as? DescriptionViewController
self.navigationController?.pushViewController(vc5!, animated: true)
}
return cell
}
I'm creating my own IntelliSense Presenter, since Visual Studio2012 support change theme, so I want my background color of the presenter can be auto-changed when the theme been changed. Is there a way to track the theme changes event, or get the current color theme of the Visual Studio?
Yes, this is possible. I had to solve a similiar issue with one of my extensions...
The current theme is stored in the Windows Registry; so I implemented the following utility class.
public enum VsTheme
{
Unknown = 0,
Light,
Dark,
Blue
}
public class ThemeUtil
{
private static readonly IDictionary<string, VsTheme> Themes = new Dictionary<string, VsTheme>()
{
{ "de3dbbcd-f642-433c-8353-8f1df4370aba", VsTheme.Light },
{ "1ded0138-47ce-435e-84ef-9ec1f439b749", VsTheme.Dark },
{ "a4d6a176-b948-4b29-8c66-53c97a1ed7d0", VsTheme.Blue }
};
public static VsTheme GetCurrentTheme()
{
string themeId = GetThemeId();
if (string.IsNullOrWhiteSpace(themeId) == false)
{
VsTheme theme;
if (Themes.TryGetValue(themeId, out theme))
{
return theme;
}
}
return VsTheme.Unknown;
}
public static string GetThemeId()
{
const string CategoryName = "General";
const string ThemePropertyName = "CurrentTheme";
string keyName = string.Format(#"Software\Microsoft\VisualStudio\11.0\{0}", CategoryName);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
return (string)key.GetValue(ThemePropertyName, string.Empty);
}
}
return null;
}
}
Okay; this just helps to figur out the current settings... listening for the theme changed notification is a bit trickier. After your package is loaded, you must obtain an IVsShell instance via the DTE; once you have this object you can utilize the AdviceBroadcastMessages method to subscribe for event notifications. You have to provide an object whose type implements the IVsBroadcastMessageEvents interface...
I don´t want to post the whole implementation, but the following lines might illustrate the key scenario...
class VsBroadcastMessageEvents : IVsBroadcastMessageEvent
{
int IVsBroadcastMessageEvent.OnBroadcastMessage(uint msg, IntPtr wParam, IntPtr lParam)
{
const uint WM_SYSCOLORCHANGE = 0x15;
if (msg == WM_SYSCOLORCHANGE)
{
// obtain current theme from the Registry and update any UI...
}
}
}
Consider implementing IDisposable on that type as well, in order to be able to unsubscribe from the event source, when the package gets unloaded.
This is how I subscribe for event notifications...
class ShellService
{
private readonly IVsShell shell;
private bool advised;
public ShellService(IVsShell shellInstance)
{
this.shell = shellInstance;
}
public void AdviseBroadcastMessages(IVsBroadcastMessageEvents broadcastMessageEvents, out uint cookie)
{
cookie = 0;
try
{
int r = this.shell.AdviseBroadcastMessages(broadcastMessageEvents, out cookie);
this.advised = (r == VSConstants.S_OK);
}
catch (COMException) { }
catch (InvalidComObjectException) { }
}
public void UnadviseBroadcastMessages(uint cookie)
{
...
}
}
Keep the value of the cookie parameter; you´ll need it to successfully unsubscribe.
Hope that helps (-:
Just wanted to put an update just in case anyone else comes along.. #Matze and #Frank are totally right.. However in VS 2015.. they added a easy way to detect the theme change. So you need to include PlatformUI an dyou get a super easy event
using Microsoft.VisualStudio.PlatformUI;
....
//Then you get an event
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
You should make sure your control is disposable so you can unsubscribe from the event...
BONUS!
It also give you easy access to the colors.. even if the user has changed them from the default .. so you can do stuff like this in when set your colors
var defaultBackground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
var defaultForeground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey);
For VS 2015 this has changed, the solution #Matze has still works but you need to update the GetThemeId() function to check for the version and if it's 14.0 (VS2015) look in a different place in the registry. The way the value is stored has changed also, it's still a string but now contains other values seperated by a '*'. The theme guid is the last value in the list.
if (version == "14.0")
{
string keyName = string.Format(#"Software\Microsoft\VisualStudio\{0}\ApplicationPrivateSettings\Microsoft\VisualStudio", version);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
var keyText = (string)key.GetValue("ColorTheme", string.Empty);
if (!string.IsNullOrEmpty(keyText))
{
var keyTextValues = keyText.Split('*');
if (keyTextValues.Length > 2)
{
return keyTextValues[2];
}
}
}
}
return null;
}