Showing posts with label cocoa. Show all posts
Showing posts with label cocoa. Show all posts

Tuesday, December 5, 2017

JavaScript not executed in WKWebView

Leave a Comment

Since I'm going a bit crazy with this one, I've decided to give it another try and post about it here.

So...

I have a simple Swift/Cocoa application with a WKWebView in it.

I load a local HTML file (which - along with the rest of the .css/.js dependencies - is being copied to the bundle inside a /web folder).

Here's the complete code:

<!DOCTYPE html> <html>     <head>         <title>IBAN Validator</title>         <meta name="viewport" content="width=device-width"/>         <meta charset="UTF-8">          <link href="style/font-awesome.min.css" type="text/css" rel="stylesheet"/>         <link href="style/electriq.css" type="text/css" rel="stylesheet"/>         <link href="style/custom.css" type="text/css" rel="stylesheet" />     </head>     <body>         <!-- window/ -->         <div class="window">             <div class="content" style="text-align: center">                 <div class="panel">                     <input id="iban" type="text" style="text-align:center;"><br/>                     <div style="position: relative; max-width: 150px; width: 100%; margin: 0 auto">                         <a id="validateButton" href="#" class="button" style="width:150px;">Validate</a>                         <span id="resultValid" style="position:absolute; left: calc(100% + 20px); top: 10%; color: green; font-size: 20px; display:none;"><i class="fa fa-check"></i></span>                         <span id="resultInvalid" style="position:absolute; left: calc(100% + 20px); top: 10%; color: red; font-size: 20px; display:none;"><i class="fa fa-close"></i></span>                     </div>                 </div>             </div>         </div>         <!-- /window -->          <div id="loader_overlay" style="padding-top:10%">             <i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i><br/>         </div>          <!-- scripts/ -->         <script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>         <script src="jquery.min.js" type="text/javascript"></script>         <script>             if (typeof window.jQuery !== 'undefined') {                 window.document.getElementById("loader_overlay").innerHTML += ".";             } else {                 window.document.getElementById("loader_overlay").innerHTML += "x";             }         </script>         <script src="handlebars.min.js" type="text/javascript"></script>         <script>             if (typeof window.Handlebars !== 'undefined') {                 window.document.getElementById("loader_overlay").innerHTML += ".";             } else {                 window.document.getElementById("loader_overlay").innerHTML += "x";             }         </script>         <script src="bridgecommander.js" type="text/javascript"></script>         <script>             if (typeof window.BridgeCommander !== 'undefined') {                 window.document.getElementById("loader_overlay").innerHTML += ".";             } else {                 window.document.getElementById("loader_overlay").innerHTML += "x";             }         </script>         <script src="iban.js" type="text/javascript"></script>         <script>             if (typeof window.IBAN !== 'undefined') {                 window.document.getElementById("loader_overlay").innerHTML += ".";             } else {                 window.document.getElementById("loader_overlay").innerHTML += "x";             }         </script>         <!-- <script src="app.js" type="text/javascript"></script> -->         <script>              // Generated by CoffeeScript 2.0.2             var doValidate;              window.appLoaded = true;             window.document.getElementById("loader_overlay").innerHTML += ".";             BridgeCommander.call("echo", "Before: onload");             window.document.getElementById("loader_overlay").innerHTML += ".";              window.onload = function() {               BridgeCommander.call("echo", "Inside: onload");               document.getElementById("loader_overlay").style.display = 'none';               return $("#validateButton").on("click", doValidate);             };             window.document.getElementById("loader_overlay").innerHTML += ".";             BridgeCommander.call("echo", "After: onload");              doValidate = function() {               var iban, valid;               iban = $("#iban").val();               valid = IBAN.isValid(iban);               if (valid) {                 $("#resultValid").show();                 $("#resultInvalid").hide();                 $("#validateButton").removeClass("invalid").addClass("valid");                 BridgeCommander.call("echo", `Validating: ${iban}, Result: valid`);               } else {                 $("#resultValid").hide();                 $("#resultInvalid").show();                 $("#validateButton").removeClass("valid").addClass("invalid");                 BridgeCommander.call("echo", `Validating: ${iban}, Result: invalid`);               }               setTimeout(function() {                 $("#validateButton").removeClass("valid").removeClass("invalid");                 $("#resultValid").hide();                 return $("#resultInvalid").hide();               }, 3000);               return false;             };             window.document.getElementById("loader_overlay").innerHTML += ".";              if (typeof window.appLoaded !== 'undefined') {                 window.document.getElementById("loader_overlay").innerHTML += ".";             } else {                 window.document.getElementById("loader_overlay").innerHTML += "x";             }         </script>         <script>if (window.module) module = window.module;</script>         <!-- /scripts -->     </body>  </html> 

Important Note: Here (meaning on my Mac - and everyone's Mac with 10.3.1 I've tried this on) everything works fine. When I upload the exact same binary to the App Store for review, I keep getting the same "error" screenshot, signifying none of the code within my last <script></script> block gets executed. (after the window.appLoaded = true part).


What could be going on? I've literally tried anything to debug this (hence, the numerous window.document.getElementById thing, adding dots to make sure everything worked), but still nothing.

As you can see, I'm loading several scripts (which according to my tests load fine), and I also have several pieces of inline JS code (which still work fine). Except for the last one! (which, no matter what, even from an external file, seems to refuse to load...)

Again, I thought about sth being cached, I don't know, but I remind you that it - apparently -- works everywhere apart from the Review team's machine :S

Any idea would be welcome!


P.S. In case something is not clear, please feel free to ask me anything


Update: (28/11/2017) Tried the whole thing with a simple - old-style - WebView (in case it had to do with the WKWebView) and still my app gets rejected. Or to be precise, my app (exact version, same everything) runs fine everywhere, except for the guy that reviews it.

1 Answers

Answers 1

This could be because the browser does not know how to parse the contents of the script tags

<script></script> tags require the type attribute on them most browsers will assume it's the same as the last one but as none of your code containing script tags specifies the type it might not know how to parse them the script support more than javascript for example VBScript to whenever you open a <script> for javascript it should be <script type="text/javascript">

The other problem could be the window.onload I would recommend you change it to the DOMContentLoaded event and use a closure to make sure it executes the code.

More so why are you not loading jQuery in the head tag where it's supposed to be loaded move <script src="jquery.min.js" type="text/javascript"></script> to inside the <head></head>.

Following on from this if you have jQuery why are you mixing jQuery and pure Javascript if you have jQuery use it's smaller code and cleaner

 (function($){      $(function(){          window.appLoaded = true;          $("loader_overlay").append(".");          BridgeCommander.call("echo", "Before: onload");          $("loader_overlay").append(".");           $("loader_overlay").append(".");          BridgeCommander.call("echo", "After: onload");           function doValidate() {              var iban, valid;              iban = $("#iban").val();              valid = IBAN.isValid(iban);              if (valid) {                  $("#resultValid").show();                  $("#resultInvalid").hide();                  $("#validateButton").removeClass("invalid")                                      .addClass("valid");                  BridgeCommander.call("echo", `Validating: ${iban}, Result: valid`);              } else {                  $("#resultValid").hide();                  $("#resultInvalid").show();                  $("#validateButton").removeClass("valid")                                      .addClass("invalid");                  BridgeCommander.call("echo", `Validating: ${iban}, Result: invalid`);              }               setTimeout(function() {                  $("#validateButton").removeClass("valid")                                      .removeClass("invalid");                  $("#resultValid").hide();                  return $("#resultInvalid").hide();              }, 3000);              return false;         };           (function(doValidate) {              BridgeCommander.call("echo", "Inside: onload");              $("loader_overlay").css("display",'none');              return $("#validateButton").on("click", doValidate);          })(doValidate);           window.document.getElementById("loader_overlay").innerHTML += ".";           if (typeof window.appLoaded !== 'undefined') {              window.document.getElementById("loader_overlay").innerHTML += ".";          } else {              window.document.getElementById("loader_overlay").innerHTML += "x";          }          if (window.module){ module = window.module; }     }); }); 

On another note please get rid of all your file loading checks. so all of the following code blocks

if (typeof window.Handlebars !== 'undefined') {     window.document.getElementById("loader_overlay").innerHTML += "."; } else {     window.document.getElementById("loader_overlay").innerHTML += "x"; } 

They are only needed for debugging and you know it's Web Kit if the file loads on one it loads on all. so you don't need these check's they are just using phone processing power and adding work to your app for no good reason. other than to put a . in the overlay...

and again all <script src=... should be inside the <head> tags, in this case, all of your code should be inside the script tags and using jQuery read as my above version does.

How are you testing this App on the Mac are you using the iPhone emulator? or a real iPhone to test i would always recommend the latter. and have you tested in on one of these as you don't seem to say you have, if not get a device registered for testing on your developer account create the keys to test and build a test version then use Safari or chromes remote debugging tools on the WebView and make sure it all works.

Read More

Sunday, November 19, 2017

NSTableView inside the NSCollectionViewItem, how to make context menu work?

Leave a Comment

I have NSTableView nested inside NSCollectionViewItem of NSCollectionView. I've tried to implement the context menu for the table, but it seems not to work properly in this situation.

The problem is that while the menu is shown on Control click, clickedRow value is always -1, and in line with that the row is never highlighted.

I've tried to subclass the NSTableView and override menuForEvent and it allows me to capture the real value of the clicked row, but I still have no visual feedback. I may implement some custom view for that, but I hope there is some better way to do that (somehow to programmatically patch this problem and make NSTableView aware that the row is clicked). Someone knows how to do it?

0 Answers

Read More

Friday, September 29, 2017

save a web view content as pdf file

Leave a Comment

Notice: I working with swift 4 for osx. I would like to generate a pdf file from a WebView.

At the moment my WebView load a html-string and show this successfully. If I press a button, the print panel will open and is ready to print my WebView content in the correct format.

This is my print code:

var webView = WebView() var htmlString = "MY HTML STRING"  override func viewDidLoad() {     webView.mainFrame.loadHTMLString(htmlString, baseURL: nil) }  func webView(_ sender: WebView!, didFinishLoadFor frame: WebFrame!) {      let printInfo = NSPrintInfo.shared     printInfo.paperSize = NSMakeSize(595.22, 841.85)     printInfo.isHorizontallyCentered = true     printInfo.isVerticallyCentered = true     printInfo.orientation = .portrait     printInfo.topMargin = 50     printInfo.rightMargin = 0     printInfo.bottomMargin = 50     printInfo.leftMargin = 0     printInfo.verticalPagination = .autoPagination     printInfo.horizontalPagination = .fitPagination     //webView.mainFrame.frameView.printOperation(with: printInfo).run()      let printOp: NSPrintOperation = NSPrintOperation(view: webView.mainFrame.frameView.documentView, printInfo: printInfo)     printOp.showsPrintPanel = true     printOp.showsProgressPanel = false     printOp.run()  } 

enter image description here

Now I would like to have another button, which save the content directly as a pdf file.

I tried this:

let pdfData = webView.mainFrame.frameView.documentView.dataWithPDF(inside: webView.mainFrame.frameView.documentView.frame) let document = PDFDocument(data: pdfData) let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("myPDF.pdf") document?.write(to: fileURL) 

But the result of my pdf looks horrible:

enter image description here

Have anybody an idea?

UPDATE This is a part of my web view result:

enter image description here

and that is the result of my print preview / pdf file /the color is missing, but now everywhere. the "logo"(picture) will show with color: enter image description here

1 Answers

Answers 1

The problem is that dataWithPDF uses the view's current frame to decide how wide the generated PDF should be. Since your WebView's frame in your app is probably skinnier than an 8.5/11" page, you're getting a PDF that is inadequately wide. You could adjust the WebView's frame to the right size, make the PDF, and then adjust it back, or you could create a new WebView, render it offscreen, set it to the appropriate size, and create the PDF. That's a bit of a pain in the backside, though. Wouldn't it be great if there were a way to programatically do what the "PDF" button in the Print dialog does, since the print system handles all this stuff for you automatically?

Well, turns out you can! But you have to dive into the poorly-documented world of Core Printing.

func makePDF(at url: URL, for webView: WebView, printInfo: NSPrintInfo) throws {     webView.preferences.shouldPrintBackgrounds = true      guard let printOp = webView.mainFrame.frameView.printOperation(with: printInfo) else {         throw MyPrintError.couldntGetPrintOperation // or something like this     }      let session = PMPrintSession(printOp.printInfo.pmPrintSession())     let settings = PMPrintSettings(printOp.printInfo.pmPrintSettings())      if PMSessionSetDestination(session,                                settings,                                PMDestinationType(kPMDestinationFile),                                kPMDocumentFormatPDF as CFString,                                url as CFURL) != noErr {         throw MyPrintError.couldntSetDestination // or something like this     }      printOp.showsPrintPanel = false     printOp.run() } 

The key is the PMSessionSetDestination call, which allows us to configure the print session to print to a PDF instead of to an actual printer. Then we just tell NSPrintOperation not to show the print panel, run the operation, and presto! PDF printed.

Read More

Monday, June 19, 2017

macOS App: handling key combinations bound to global keyboard shortcuts

Leave a Comment

In some apps, it makes sense for the app to directly handle keyboard shortcuts which are otherwise bound to system wide combinations. For example, ⌘-Space (normally Spotlight) or ⌘-Tab (normally app switcher). This works in various Mac apps, such as VMWare Fusion, Apple's own Screen Sharing and Remote Desktop clients (forwarding the events to the VM or server, respectively, instead of handling them locally), and also some similar third-party apps in the App Store.

We would like to implement such a mode in the app we're working on, but are having a hard time working out how to do it. I should point out that the app in question is a regular foreground app, is sandboxed, and any solution must comply with App Store rules. The fact that other apps on the store can do it implies that this must be possible.

To be clear, we want to:

  • Detect and handle all key presses, including those bound to global shortcuts.
  • Prevent global shortcuts from triggering their globally bound effect.

Apple's Event Architecture document suggests that the foreground application should already be receiving these events. (It only talks about earlier levels handling things such as the power and eject buttons, which is fine.) It goes on to suggest, and the key events document also implies that NSApplication's sendEvent: method is what detects potential shortcuts based on modifier flags, dispatching them to the windows and if that fails, on to the menu bar. It's not explicitly stated what happens to globally-bound shortcuts.

I tried subclassing NSApplication and overriding sendEvent:. No matter if I pass through all events to the superclass implementation, or if I say, filter modifier key events, when I press ⌘-Space, I receive the events for pressing and releasing the command (⌘) key, but not the spacebar. The Spotlight UI always pops up.

I haven't found much information on subclassing NSApplication and its early event handling, from Apple or otherwise. I can't seem to find out at what level global shortcuts are detected and handled.

Can someone please point me in the right direction?

Possible solutions which don't work:

Suggestions I've seen in other Stack Overflow posts but which don't apply to the other apps I've seen which do this (and which would break App Store rules):

  • Accessibilty APIs (needs special permission)
  • Event taps/hooks (needs to run as root)

Both of these would be overkill anyway, as they let you intercept all events at all times, not just while your app is the foreground app.

NSevent's addGlobalMonitorForEventsMatchingMask:handler: meanwhile doesn't prevent the global shortcut handler from firing for those events, so I didn't even bother trying it.

1 Answers

Answers 1

The normal way to achieve this is by installing a Quartz event tap. However to receive events targeting other applications, you need (as you say) to be either root, or have accessibility access enabled for your app.

It seems not possible to use an event tap with the current sandboxing rules. This is confirmed in the developer forum. The link is login only, but to quote from the thread:

Is there are any chance to handle events that comming from media keys by prevents launch iTunes. Before sandbox it was possible by create CGEventTap but now sandbox deny using hid-controll.

No, this is not currently possible within App Sandbox.

I'm not sure of another way to do this; and I'd be interested to know what apps in the App Store can?

VMWare Fusion is clearly not sandboxed, and Apple's own apps are exempt from the rules. Remember that sandboxing is only enforced on new apps added after it was introduced, in 2012. Apps added before that date do not have sandboxing enforced. See this answer.

Read More

Monday, March 20, 2017

How to securely send private data over a web socket to an objective-c client and back to the server?

Leave a Comment

I am making a wss:// connection to ratchet (a PHP socket library) using SocketRocket (an Objective-c socket library).

I plan to send private data over this socket connection and then send the data back to the server with a https:// request.


The objective-c code:

//initiate global variable @property (nonatomic) NSMutableArray* keys;  ...  //receive the private data with SocketRocket - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(nonnull NSString *)string {     [_keys insertObject:string]; }  ...  //$_POST the file data with sthttp STHTTPRequest *r = [STHTTPRequest requestWithURLString:@"https://example.com/test.php"]; r.POSTDictionary = @{ @"key":_keys[0] }; ... 

Is there any possible way that a client can intercept this private data (within reason [buffer overflow, man in the middle, etc...])?

2 Answers

Answers 1

If you are using wss:// and https:// protocols, you don't have to worry about a man in the middle attack since all the data being sent is encrypted anyway.

However if under any circumstance you have to send data over an insecure protocol or URL query string, you can encrypt the data yourself using PHP's open SSL module and send it in clear text (eg:$_GET params).

Example: http://php.net/manual/en/book.openssl.php#91210

In this example $crypttext will be binary data. This can be encoded into a base64 string and the url encoded if you need to send it via a GET or POST request.

urlencode(base64_encode($crypttext))

On the receiving end you can base64 decode and url decode to get the binary information and then decrypt the data using the private key as shown in the example.

base64_decode(urldecode($crypttext)

Answers 2

I would recommend that your certificates are all up to date and make sure your private key cert is protected and not accessible to anyone but you.

One note to remember is that if you are doing any logging, you might end up logging data that you want secure. I would double check your logging policy and make sure you are ok with it. Sometimes information will be passed along the url as query params and then those are logged to the servers log files.

If there is any history that you are saving, make sure to check that out or any caches on the mobile devices just in case.

Read More

Thursday, March 9, 2017

CGEventPost - hold a key (shift)

Leave a Comment

I'm looking for a way to design a little panel with modifier keys on it (shift, command for example) and have to possibility to click on it like a virtual keyboard.

I'd like it to have this behavior :

  • click on the virtual key (shift).
  • the shift button holds, and keeps being pressed.
  • type something with my standard keyboard.
  • click another time on the virtual shift key to release it.

here is the code I'm using :

CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef shiftKeyDown = CGEventCreateKeyboardEvent(source, (CGKeyCode)56, YES); CGEventRef shiftKeyUp = CGEventCreateKeyboardEvent(source, (CGKeyCode)56, NO);  CGEventPost(kCGAnnotatedSessionEventTap, shiftKeyDown); CGEventPost(kCGAnnotatedSessionEventTap, shiftKeyUp);  CFRelease(shiftKeyUp); CFRelease(shiftKeyDown); CFRelease(source); 

I can't find a way to keep it pressed until I click on it another time. I though "Push On Push Off" Button Cell type was the key but unfortunately no. :-)

any help ?

thanks in advance.

0 Answers

Read More

Friday, January 27, 2017

tracking misspelled words from applespell.service

Leave a Comment

I am trying to record any words that are detected as being misspelled on mac osx.

I see that AppleSpell.service is the apple system spellcheck server so ideally I'd like a way to listen to that service and record when a misspelled word is detected. https://www.dropbox.com/s/nudfn8p2y0yp95g/Screenshot%202017-01-18%2020.10.11.png?dl=0 However I can't find a source for this file or documentation for any api for it.

I've also looked at https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/SpellCheck/Tasks/CreatingSpellServer.html but it doesn't document any way to subscribe to a server/service.

Is this possible? Is there any way to listen to a service or more precisely a way to listen for and record words that are detected as being misspelled on mac os x.

If the only way to do this is to build a new spell checking service are there any good guides on how to go about doing this?

0 Answers

Read More

Thursday, April 28, 2016

Register for global file drag events in Cocoa

Leave a Comment

I'm trying to be notified when a OS X user is dragging any file in OS X, not only in my app.

My current approach was using addGlobalMonitorForEventsMatchingMask:handler: on NSEvent, as follows:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDraggedMask handler:^(NSEvent* event) {     NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard];     NSLog(@"%@", [pb propertyListForType:NSFilenamesPboardType]); }]; 

This works partially - the handler is being called when I start dragging a file from my desktop or Finder, however it also is being called when I perform every other operation that contains a left-mouse-drag, e.g. moving a window. The issue is that the NSDragPboard still seems to contain the latest dragged file URL e.g. when I let off the file and start moving a window, which makes it hard to distinguish between these operations.

TL;DR - I am interested in file drag operations system-wide. I do not need any information about the dragged file itself, just the information that a file drag operation has been started or stopped. I would appreciate any hint to a possible solution for this question.

1 Answers

Answers 1

After having talked to Apple DTS, this is most likely a bug. I have filed rdar://25892115 for this issue. There currently seems to be no way to solve my original question with the given API.

To solve my problem, I am now using the Accessibility API to figure out if the item below the cursor is a file (kAXFilenameAttribute is not NULL).

Read More

Wednesday, April 27, 2016

How to loop through all Mac desktop spaces

Leave a Comment

I'm trying to set a desktop background for all screens AND spaces (preexisting and new). However, I can't seem to find a way to set the background for all the existing spaces (and any new spaces created use the old background).

Here is what I have so far:

let sqlData = NSMutableArray() let paths = NSSearchPathForDirectoriesInDomains(.ApplicationSupportDirectory, .UserDomainMask, true) let appSupportDirectory = paths.first! as NSString let dbPath = appSupportDirectory.stringByAppendingPathComponent("Dock/desktoppicture.db") as NSString  var db: COpaquePointer = nil if sqlite3_open(dbPath.UTF8String, &db) == SQLITE_OK {     var statement: COpaquePointer = nil      if sqlite3_exec(db, "DELETE FROM data", nil, nil, nil) != SQLITE_OK {         let errmsg = String.fromCString(sqlite3_errmsg(db))         print("error deleting table row: \(errmsg)")     }      if sqlite3_exec(db, "INSERT INTO DATA (VALUE) VALUES ('\(getBackgroundImagePath())');", nil, nil, nil) != SQLITE_OK {         let errmsg = String.fromCString(sqlite3_errmsg(db))         print("error inserting table row: \(errmsg)")     }      let workspace = NSWorkspace.sharedWorkspace()      for screen in NSScreen.screens()! {         do {             let options = workspace.desktopImageOptionsForScreen(screen)             try workspace.setDesktopImageURL(NSURL(fileURLWithPath: getBackgroundImagePath()), forScreen: screen, options: options!)         } catch let error as NSError {             NSLog("\(error.localizedDescription)")         }     }      system("/usr/bin/killall Dock") }  sqlite3_close(db) 

Note: I update the .db file found in ~/Library/Application Support/Dock/desktoppicture.db. Since this doesn't actually update the background, I then proceed to loop through each screen and set them manually.

Although this changes all of the screen's backgrounds, any non-active spaces are not changed, and any new spaces created use the old background.

I'm using this code within a small app I made on GitHub, and this is an issue a user reported. You can find the issue here (with a terminal solution).

Apple has a seemingly relevant project here, but even they don't update multiple spaces.

Also, if you update the background through the default mac settings app, it also doesn't change pre-existing spaces. Is it impossible?

1 Answers

Answers 1

OS X Desktop Picture Global Updating Across Spaces

(It's not impossible, however, definitely possible even without using loops.)

In desktoppicture.db an update query can be run on the data table with the path of the new image (value). It shouldn't be necessary to delete and then insert the values, or use loops. Using an unscoped query calls update, and by doing so it will update every row in the data table.

func globalDesktopPicture {      let paths: [String] = NSSearchPathForDirectoriesInDomains(.ApplicationSupportDirectory,                                                               .UserDomainMask, true)     let appSup: String = paths.first!     let dbPath: String = (appSup as NSString).stringByAppendingPathComponent("Dock/desktoppicture.db")      let image : String = "/path/to/new/image"     let dbase = try? Connection("\(dbPath)")     let value = Expression<String?>("value")         let table = Table("data")     try! dbase!.run(table.update(value <- image))      system("/usr/bin/killall Dock") } 

Keep in mind that this example is quite minimal and should include some error checking, etc. I'll leave those details up to you though, since this is really about showing how to update the desktop picture across all spaces. You might find some of the other information below helpful as well.

Read More

IKImageBrowserView on retina screen

Leave a Comment

Has anyone successfully used an IKImageBrowserView with a Retina Mac? What I get is that the image size is wildly misinterpreted. Previously I was using CGImage images which don't have a logical size, so it makes sense that the browser can't draw the at the right size. However, I've switched to NSImage, created using -initWithCGImage:size: and that still doesn't work right.

My images are 244x184 pixels and should be drawn at a logical size of 122x92. When passing 122x92 as the size, they are drawn way too large, at about 180 pixels wide. If I pass exactly half this, 61x46, the size is correct, but the image looks downscaled and not sharp. If I pass 122x92 and run with NSHighResolutionCapable set to NO in Info.plist, everything works well.

My conclusion is that IKImageBrowserView is not Retina compatible even with the 10.10 SDK on a Retina MacBook Pro running OS X 10.11. Or am I missing something? Any pointers would be appreciated!

0 Answers

Read More

Friday, April 22, 2016

OS X app freeze when closing window during CALayer animation

Leave a Comment

I made a custom NSControl to use it as a custom layer backed button.

When the MyButton instance receives mouseDown and mouseUp events it changes its backgroundLayer's backgroundColorand its textLayer's foregroundColor.

It's using layers so the changes are implicitly animated.

But on mouseUp if the mouse is inside the MyButton's instance frame I call the linked action through sendAction(_:to:) method.

I linked the action to a method closing the current window and opening another one but sometimes the app freeze after the second window shows up.

I tried several things and it seems related to the layer animations, maybe something to do before closing the window that I'm not aware of.

You can find an example project here : https://dl.dropboxusercontent.com/u/378166/CALayerFreeze.zip
(note that you'll sometimes have to try several times before the bug occurs)

Here's the code for MyButton.

class MyButton: NSControl {     let title = "Click me!"      // Init     required init?(coder: NSCoder) {         super.init(coder: coder)         setup()     }      deinit {         trackingAreas.forEach { self.removeTrackingArea($0) }     }      // Layer + Tracking Area configuration     var backgroundLayer = CALayer()     var textLayer = CATextLayer()      func setup() {         wantsLayer = true          backgroundLayer.frame = NSRect(origin: .zero, size: frame.size)         backgroundLayer.backgroundColor = NSColor.whiteColor().CGColor         layer?.addSublayer(backgroundLayer)          textLayer.frame = NSRect(origin: .zero, size: frame.size)         textLayer.string = title         textLayer.foregroundColor = NSColor.blackColor().colorWithAlphaComponent(0.64).CGColor         layer?.addSublayer(textLayer)          addTrackingArea(             NSTrackingArea(                 rect: bounds,                 options: [.MouseEnteredAndExited, .EnabledDuringMouseDrag, .ActiveInKeyWindow],                 owner: self,                 userInfo: nil             )         )     }      // States     private func normal() {         // ——— COMMENTING THIS MAKES THE BEACHBALL GO AWAY         backgroundLayer.backgroundColor = NSColor.whiteColor().CGColor         textLayer.foregroundColor = NSColor.blackColor().colorWithAlphaComponent(0.64).CGColor     }      private func highlight() {         // ——— COMMENTING THIS MAKES THE BEACHBALL GO AWAY         backgroundLayer.backgroundColor = NSColor.grayColor().CGColor         textLayer.foregroundColor = NSColor.whiteColor().colorWithAlphaComponent(0.64).CGColor     }      // Tracking events     var isMouseDown = false      override func mouseDown(theEvent: NSEvent) {         super.mouseDown(theEvent)         isMouseDown = true         highlight()     }      override func mouseUp(theEvent: NSEvent) {         super.mouseUp(theEvent)         isMouseDown = false         normal()          if frame.contains(convertPoint(theEvent.locationInWindow, toView: self)) {             sendAction(action, to: target)         }     } } 

0 Answers

Read More

Saturday, April 9, 2016

bringing up a InputWindow on OSX for text input while typing text with asian laguage

Leave a Comment

I am working on a text editor written with C++ engine and Qt for UI. I want to allow the user to write with any of the input source ( keyboard of any language ). It was all good till the time I was supporting languages which has 1-1 keyboard mapping ( e.g. French/Russian keyboard ). I had an eventFilter installed on my Qwidget on which I was rendering the text and was capturing the keyboard inputs in QEvent::InputMethod

But when I started up with Asian languages ( like japanese/chinese ) I am not able to support all the features required for text editing with such language, a typical example of such case is the split underline when user writes some text with Japanese( Hiragana IME ) and presses space key which helps user in determining what all characters are to be replaced with the content on prediction dialog.See Image below: some text written on TextEdit application with Japanese Input Method( Hiragana ), notice the split underlines coming up when user hits a spacebar,:

after struggling a while I figured out that Qt does not provide enough information about the splits or length of the string which is being replaced and I give up the idea to create all these visual appearance myself.

But then I discovered that some of the applications uses some OS specific input method to handle such complex text. An example is the OSX Finder, if we change the input method to Japanese ( Hiragana ) and start typing when a finder window is in focus, it pops up a floating window which accepts all my inputs and passes it to finder. See the image belowenter image description here

I dig more and I figured out that there was such a framework which was available earlier as Text Services Manager with a lot of documentation ( "http://mirror.informatimago.com/next/developer.apple.com/technotes/te/te_27.html#Downloads" ) which could have done this trick very easily for me BUT this API has been deprecated and no more available.

What I am looking for now is an alternative for this deprecated API. Does any body know whether we have a cocoa API which can help me with bringing this operating system input method component for the easy text input.

Any help/suggestions are welcome.

1 Answers

Answers 1

Ok, I found the solution. I was ignorant to say that Qt does not provide enough information about these splits, Qt has a way to provide this support by using the caret position. So to conclude, the information of the text to be displayed can be easily retrieved by:

for( auto value : inEvent->attributes() )     {         if( value.type == QInputMethodEvent::Cursor )         {             std::cout<<" length "<< value.length;             std::cout<<" start "<< value.start;         }     } 

here start is the position of the cursor, once this position is clear it is easy to determine how much length of text should be underlined so as to give clear indication to the user.

Read More

Friday, April 1, 2016

How to encrypt data in core data (sqllite) in OS-X application

Leave a Comment

I have found that if I use transformable type of attributes and NSXMLStoreType my data is encrypted, that is attributes that has been of transformable type, are not readable. There is no need of doing anything else, no code is required. Please note that I am working on OS-X application that uses core data.

However, if I change my store type to NSSQLiteStoreType, that is not the case.

I can open the database with sqllitebrowser, select the transformable field, and If I click on export button, in the generated text file, I can read the value normally, that is the value (data) is not encrypted.

I have asked the same question about 4 months ago and I get no answer.

Also, I have found this post here on stackoverflow.

You can encrypt individual properties in your Core Data model entities by making them transformable properties, then creating an NSValueTransformer subclass which will encrypt and decrypt the data for that property.

Unlucky for me, the author of the answer, @Brad Larson, didn't provide an simple example of how this can be done.

Can anyone provide any sample code of how I can encrypt transformable properties so that It' cant be readable in any way?

1 Answers

Answers 1

you could do something like shown here Cross-platform-AES-encryption

add a new Objective-C file to project select category and NSData class call it Additions

NSData+Additions.h

#import <Foundation/Foundation.h> #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h>  @interface NSData (Additions)  #pragma mark - data encryption  + (NSData *)encrypt:(NSData *)plainText key:(NSData *)key iv:(NSData *)iv; + (NSData *)decrypt:(NSData *)encryptedText key:(NSData *)key iv:(NSData *)iv;  + (NSData *)dataFromHexString:(NSString *)string; + (NSData *)sha256forData:(id)input;  + (NSData *)generateRandomIV:(size_t)length;  @end 

NSData+Additions.m

#import "NSData+Additions.h"  @implementation NSData (Additions)  + (NSData *)encrypt:(NSData *)dataToEncrypt key:(NSData *)key iv:(NSData *)iv {      NSUInteger dataLength = [dataToEncrypt length];      size_t buffSize = dataLength + kCCBlockSizeAES128;     void *buff = malloc(buffSize);      size_t numBytesEncrypted = 0;      CCCryptorStatus status = CCCrypt(kCCEncrypt,                                      kCCAlgorithmAES128,                                      kCCOptionPKCS7Padding,                                      [key bytes], kCCKeySizeAES256,                                      [iv bytes],                                      [dataToEncrypt bytes], [dataToEncrypt length],                                      buff, buffSize,                                      &numBytesEncrypted);      if (status == kCCSuccess) {         return [NSData dataWithBytesNoCopy:buff length:numBytesEncrypted];     }      free(buff);     return nil; }  + (NSData *)decrypt:(NSData *)encryptedData key:(NSData *)key iv:(NSData *)iv {      NSUInteger dataLength = [encryptedData length];      size_t buffSize = dataLength + kCCBlockSizeAES128;      void *buff = malloc(buffSize);      size_t numBytesEncrypted = 0;     CCCryptorStatus status = CCCrypt(kCCDecrypt,                                      kCCAlgorithmAES128,                                      kCCOptionPKCS7Padding,                                      [key bytes], kCCKeySizeAES256,                                      [iv bytes],                                      [encryptedData bytes], [encryptedData length],                                      buff, buffSize,                                      &numBytesEncrypted);     if (status == kCCSuccess) {         return [NSData dataWithBytesNoCopy:buff length:numBytesEncrypted];     }      free(buff);     return nil; }  + (NSData *)dataFromHexString:(NSString *)string {      NSMutableData *stringData = [[NSMutableData alloc] init];     unsigned char whole_byte;     char byte_chars[3] = {'\0','\0','\0'};      for (int counter = 0; counter < [string length] / 2; counter++) {         byte_chars[0] = [string characterAtIndex:counter * 2];         byte_chars[1] = [string characterAtIndex:counter * 2 + 1];         whole_byte = strtol(byte_chars, NULL, 16);         [stringData appendBytes:&whole_byte length:1];     }      return stringData; }  + (NSData *)sha256forData:(id)input {     NSData *dataIn;      if ([input isKindOfClass:[NSString class]]) {         dataIn = [input dataUsingEncoding:NSUTF8StringEncoding];     } else if ([input isKindOfClass:[NSData class]]) {          NSUInteger dataLength = [input length];         NSMutableString *string = [NSMutableString stringWithCapacity:dataLength * 2];         const unsigned char *dataBytes = [input bytes];          for (NSInteger idx = 0; idx < dataLength; ++idx)             [string appendFormat:@"%02x", dataBytes[idx]];          dataIn = [string dataUsingEncoding:NSUTF8StringEncoding];     }      NSMutableData *macOut = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];      CC_SHA256(dataIn.bytes, (CC_LONG)[dataIn length],  [macOut mutableBytes]);      return macOut; }  + (NSData *)generateRandomIV:(size_t)length {     NSMutableData *data = [NSMutableData dataWithLength:length];      SecRandomCopyBytes(kSecRandomDefault, length, [data mutableBytes]);      return data; }  @end 

then you are going to need username, password, iVector and some random salt. in the salt i have used replace # with random characters but make sure that if u use % do not forget to double it %% otherwise it will be incomplete format specifier warning.

in your class use it like this

#define CC_USERNAME         @"secretName" #define CC_PASSWORD         @"secretPassword" #define CC_SALTED_STRING    [NSString stringWithFormat:@"####################%@#####################", CC_PASSWORD] 

then create NSData representation of your salted string ran through SHA256

NSData *hash = [NSData sha256forData:CC_SALTED_STRING]; 

next step is to generate 16 bytes of random generated iVector data

NSData *iVector = [NSData generateRandomIV:16]; 

and use these objects to encrypt your string. create a NSMutableData object with first 16 bytes of iVector data (make sure u use iVector object and do not generate new random or you will be unable to decrypt).

NSString *message = @"my secret message to the world"; NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding];  NSMutableData *encryptedData = [[NSMutableData alloc] initWithData:iVector];  NSData *payLoad = [NSData encrypt:messageData key:hash iv:iVector];  [encryptedData appendData:payLoad]; 

to decrypt, separate first 16 bytes and the rest of data and use it with the hash.

NSData *pureData = [encryptedData subdataWithRange:NSMakeRange(16, [encryptedData length] - 16)]; NSData *extractedVector = [encryptedData subdataWithRange:NSMakeRange(0, 16)];  NSData *decryptedData = [NSData decrypt:pureData key:hash iv:extractedVector];  NSString *decryptedMessage = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; 

you can do some extra md5 on the hash or even pack encrypted data with zlib before storing it.

enjoy your custom made crypto.

Read More

Thursday, March 24, 2016

cocoa pods Dependency management

Leave a Comment

[!] Unable to satisfy the following requirements:

  • SDWebImage (= 3.7) required by DZNPhotoPickerController/Core (1.6.0)
  • AFNetworking (~> 3.0) required by Podfile
  • AFNetworking (~> 1.3.3) required by DZNPhotoPickerController (1.0.2)

PodFile

pod 'DZNPhotoPickerController' pod 'ZXingObjC', '~> 3.0' pod 'vfrReader','~>2.8.6' pod 'Mantle' pod 'PPSSignatureView' pod 'AFNetworking','~>3.0' pod 'JNKeychain' pod 'SVProgressHUD' pod 'ZipArchive' 

I want to update AFNetworking.

Help me to solve this

3 Answers

Answers 1

To fix this you might need to

Fork the DZNPhotoPickerController repo and modify the dependency in the Pod spec and then use your forked repo's git location in the pod file. Not a great solution for the long term but works.

For instance, The Pod spec here should have the line#33 which is

ss.dependency 'AFNetworking' 

should be changed to

ss.dependency 'AFNetworking', '~> 3.0'  

in your forked repo.

Then use in your Pod file as below

pod 'DZNPhotoPickerController', :git => 'https://github.com/yourUsername/DZNPhotoPickerController.git' 

Your forked repo is not guaranteed to work out of the box if the project relies on legacy dependency code, if any. In this particular case, if it uses any methods of AFNetworking that is removed in 3.0 it will not work.

Answers 2

To update a single AFNetworking pod,

pod update AFNetworking 

with above code, cocoapods will find a latest updated pod version and update your pod.

To update your all pods you can use

pod update 

this will update your all the pods.

Answers 3

The issue looks like 'DZNPhotoPickerController' has dependancy of ss.dependency 'AFNetworking', '~> 2.6.0' so when you are trying to update AFNetworking to version 3.0 it shows the wrong version error.

If you didn't check it recently the 'DZNPhotoPickerController' has also updated it's podspec to make AFNetworking to 3.0 so you should try pod update now it may fix your issue as both dependancy needs the same version

If the above and the fork 'DZNPhotoPickerController' in to newer one didn't work

  • Please remove the 'DZNPhotoPickerController' from the cocoapods
  • update the cocoapods by pod update
  • if it's successful add the 'DZNPhotoPickerController' dependancy again in the cocoapods and install that again it may solve your problem
Read More