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

Monday, January 15, 2018

Deleting cookies with WKHTTPCookieStore

Leave a Comment

I'm using the new WKHTTPCookieStore class in order to inject and delete cookies from WKWebViews in an app.

All of the WKWebViews share a common WKWebViewConfiguration so that they can share a common cookie store.

Injecting cookies works fine using the add() method, and each of the web views can see the new cookies and send them with their requests. Deleting cookies seems to be a problem - the web views all still see the supposedly deleted cookie, and continue to send it with each request:

let cookieStore = self.webkitConfiguration.websiteDataStore.httpCookieStore cookieStore.getAllCookies { (cookies) in     for cookie:HTTPCookie in cookies {         if cookie.name == "CookieIWantToDelete" {             cookieStore.delete(cookie, completionHandler: {                 self.webView.reload() //Deleted cookie is still sent with this request             })         }     } } 

I can work around it by trashing all of the cookies in the WKWebsiteDataStore, but it seems a bit overkill.

Any ideas?

1 Answers

Answers 1

You need to clear WKWebView cache before reloading by using URLCache.shared.removeAllCachedResponses(), for exampe, or use self.webView.reloadFromOrigin() to load fresh data.

Read More

Thursday, September 14, 2017

hide .json files when included in Cocoa Touch Framework

Leave a Comment

I would like to release a cocoa touch framework including some json files which contain logic I don't want the framwork's user to see; unfortunately inside the .framework file there is still the json visible.

I thought about including it in a swift file:

struct JsonProvider {     static let json = "..." } 

but my json is that large that the file isn't usable any more. I didn't find a solution to command line compile and then include it.

Is there a solution to one of the two problems, i.e.

  • hide the json inside the framework or
  • precompile a swift file and then add it to the framework?

2 Answers

Answers 1

So I created a JSONEncoder struct as follows:

public struct JSONEncoder {    private static var JSON = ""   public static var JSONData = [UInt8]()   private static var cipher = [UInt8]()   public static var encryptedJSONData = [UInt8]()   public static var decryptedJSONData = [UInt8]()   private static var decryptedJSON = ""    public static func JSONFileToString() {             guard let JSONFilePath = Bundle.main.url(forResource: "JSONFile", withExtension:"json"), let cipherFilePath = Bundle.main.url(forResource: "JSONCipher", withExtension:"txt") else {       return     }      do {       let text = try String(contentsOf: cipherFilePath, encoding: String.Encoding.utf8)                 cipher = [UInt8](text.utf8)       let data = try Data(contentsOf:JSONFilePath)        if let JSON = String(data: data, encoding: String.Encoding.utf8) {         self.JSON = JSON         JSONData = [UInt8](JSON.utf8)         //print("data read: \(JSON)")       } else {         print("Data to string conversion failed")       }     } catch {       print(error.localizedDescription)     }   }    public static func encodeJSON() {     // encrypt bytes     for t in JSONData.enumerated() {       encryptedJSONData.append(t.element ^ cipher[t.offset])     }     print(encryptedJSONData)   }    public static func decodeJSON() {     for t in encryptedJSONData.enumerated() {       decryptedJSONData.append(t.element ^ cipher[t.offset])     }     decryptedJSON = String(bytes: decryptedJSONData, encoding: String.Encoding.utf8)!     //print(decryptedJSON)   }  } 

To set this up you need to add a JSONFile.json file containing the JSON you want to encode. Add it to copy bundle resources. Repeat this for the cipher text file JSONCipher.txt. Make sure this file contains no speech marks, as this brings errors and that it is sufficiently large enough to encode the JSON. Don't make it too large as otherwise you get a memory warning.

You can play around with this, to encode a JSON file with XOR, using

JSONEncoder.JSONFileToString() JSONEncoder.encodeJSON() 

This will then print to the console, and you can copy this code to a txt file attached to your framework.

For decrypting this file, you can test as follows:

JSONEncoder.decodeJSON() 

(Uncommenting the print statement in decodeJSON in the JSONEncoder struct, can test this works)

If your JSON file is too large for this program, you can actually use this tool: https://www.browserling.com/tools/xor-encrypt, and similar XOR Decrypter on the same domain. You can then use the output from this encoder to add the encrypted JSON file to your framework. Just make sure you use the same key when you decode the json!

Note that this stores your text file in the actual project: this is potentially insecure. Maybe host the JSONCipher.txt file on a server and fetch that using an authenticated access token. The code I provided can be modified to allow for this.

I hope this has pointed you in the right direction for code obfuscation.

Answers 2

You can add json file content to sources as base64 encoded string.

struct JSONFiles {     internal static let fileOneBase64String = "iVBORw0KGgoAAAANSUhEUgAAAMEAAADLCAYAAADX..."     static var fileOneJSON: [String: Any] {         let data = Data(base64Encoded: fileOneBase64String)!         return try! JSONSerialization.jsonObject(with: data, options: []) as!  [String: Any]     } } 

You can use it later anywhere in you framework by

print(JSONFiles.fileOneJSON) 

Hope it help you

UPDATE: Why as base64 encoded string?

The main benefit base64 encoded string - you don't need to escape special symbols contained in JSON string. Don't need to remove new line symbols/intendation symbols from json string before add to swift source file. See example

let string: String = "{ \"name\":\"John\", \"email\": \"hello@stackoverflow\" \"age\":30, \"car\":null }" 

But if json is more complex? If it contains 10 nested levels, arrays, dictionaries?

You can cover by UnitTests that value contained in fileOneBase64String can be decoded to JSON object

import XCTest @testable import MyAwesomeFramework  class FrameworkTests: XCTestCase {      func testThatBase64EncodedJSONStringCanBeDecoded() {         let data = Data(base64Encoded: JSONFiles.fileOneBase64String)         XCTAssertNotNil(data, "Unable to convert base64 string to Data")         do {             _ = try JSONSerialization.jsonObject(with: data, options: [])         } catch {             XCTFail("Expected decoded JSON ")         }     } } 
Read More

Monday, June 12, 2017

Embedding Share Icons Inline Within Another View

Leave a Comment

I am attempting to implement a "Share" feature in my iOS app with a similar look and feel to that of the Google Photos iOS app:

enter image description here

The bottom two rows of icons are what I care about. They look nearly identical to those displayed when using the UIActivityViewController.

In the Google Photos app, these icons appear inline with the "select photos" portion of the screen (e.g. you can still interact with the upper portion of the screen). However, the documentation for the UIActivityViewController states that "On iPhone and iPod touch, you must present [the view controller] modally."

This is the difference that is really important to me -- I'd like for the "share" icons to display inline with the rest of my content, rather than having a modal that is displayed on top of my content.

Is it possible to use the UIActivityViewController to achieve a similar effect shown in the screenshot above? If not, is there a recommended approach that I might use to implement this sort of functionality?

2 Answers

Answers 1

Okay, I thought about this and I did some intensive research on the web but nobody ever seemed to needed to modify it like you want. So here are my guesses how Google engineers solved this:

  1. They reverse engineered the UIActivityViewController and call some private APIs to get the same icons to show up and the reordering controllers
  2. They use the UIViewController transitioning API and hack the view hierarchy of a modally presented UIActivityViewController, removing the cancel button and adding some custom views on top of its view

An indicator for the second option could be that the top of the presented "sheet" has a white background while the bottom has the greyish color.

Unfortunately I'm not very fit with the transitioning API as I'm just about to learn it but my understanding is that you can provide a custom object as the transitioning delegate.

This object then gets called when you present/dismiss or push/pop a UIViewController. It will get a reference to both, the presenting and the presented view controller and you can have fun with both of their views.

This should make it "quiet" easy to remove and add some subviews, change frames and colors etc while still having all the default behavior.

I hope this answer helps you to achieve what you want. However, be aware that the structure of the controller could change at any time so always make sure to also test agains betas so that you don't get caught by surprise when apple releases an update which breaks your UI. :)

Answers 2

As discussed in another answer, reverse engineering UIActivityViewController is the only option in order to be able to achieve a similar effect. I tried this using iPhone 6s - 10.3 Simulator. The following findings may not be accurate for iOS 9.x or 11.x or above.

A. Find out all internal variables for UIActivityViewController

var variablesCount: UInt32 = 0 let variables = class_copyIvarList(UIActivityViewController.self, &variablesCount)  for i in 0..<variablesCount {     if let variable = variables?[Int(i)] {         let name = String(cString: ivar_getName(variable))         let typeEncoding = String(cString: ivar_getTypeEncoding(variable))         print("\(name)\n\(typeEncoding)\n\n")     } }  free(variables) 

The ones those got my attention at first sight are (in order) -

_activityViewController @"UIViewController"  _contentController @"_UIActivityViewControllerContentController"  _activityAlertController @"UIAlertController" 

On inspecting them further, I found out that _contentController is the one we should be looking for. We need to look one level deeper in hierarchy for UICollectionViewController to get to where we want to be.

if let activityContentController = activityVC.value(forKeyPath: "_contentController") as? UIViewController {     print("Found _contentController!")      for child in activityContentController.childViewControllers {         print(String(describing: child))          if child is UICollectionViewController {             print("Found UICollectionViewController!")                                     break         }     } } 

Why did I look for UICollectionViewController?

Debug View Hierarchy has the answer for this.

enter image description here

I tried adding this as a childViewController to my UIViewController -

self.addChildViewController(child) child.didMove(toParentViewController: self) self.view.addSubview(child.view)  child.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([     child.view.topAnchor.constraint(equalTo: self.view.topAnchor),     child.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),     child.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),     child.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),     ]) 

IT SHOWS UP CORRECTLY ONLY IF YOU HAVE LOADED/PRESENTED UIActivityViewController FIRST.

I was able to achieve this using a silent present/dismiss call -

self.present(activityVC, animated: false, completion: {     self.dismiss(animated: false, completion: nil) }) 

IS THIS APP STORE SAFE? - Most likely not.

As soon as you start stealing the view(s) or viewController(s) from UIKit's standard components, the behavior is not stable and it will break with upcoming updates for sure.

What Google Photos has is the result of way more advanced reverse engineering. In above implementation, you can't see More option screen. The hierarchy UIActivityViewController expects is broken.

Hope this helps.

Read More

Tuesday, April 19, 2016

Use a different region notation than the system's region notation

Leave a Comment

On iOS, an app can use a different language than the system language if the developer passes the NSLocale to the right instances, or else if the proper values are written to NSUserDefault's AppleLanguages key.

I don't seem to find a way to do this also for the region, the part that takes care of number formatting.

The use case here is that I want a UITextfield's DecimalPad keyboard to show a comma for a decimal no matter what the system's region is set to.

Is this possible?

1 Answers

Answers 1

You can use AppleLocale instead of AppleLanguages.

E.g. -AppleLocale nl_BE will set the locale to Dutch with region Belgium.

Read More

Sunday, April 17, 2016

locationManager: didEnterRegion: triggered without location change

Leave a Comment

I have an app that monitors some regions (geofencing) that each region represent a store, the app notifies the user whenever he approaches to a store.

For some reason, the app sends a notification every around 20 minutes when the user is already inside the region's circle.

It's all working fine, but when the user is inside a region for a long time the app will keep notifying him until he'll exit the region.

Any idea why is it happening? Thank you!

2 Answers

Answers 1

When you create a locationManager and call startUpdatingLocation, it starts giving you co-ordinates of your simulator or device continuously, with a very slight differences in the co-ordinate's values. You need to call stopUpdatingLocation.

 For your case you need to stopUpdatingLocation when user exist the region or you can record the last location like this:   -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations  {   CLLocation *newLocation = [locations lastObject]; [locationManager stopUpdatingLocation];   } 

Answers 2

We have the same thing happening. We just keep track of the region coordinate and when the new one comes in we have a method to determine if the new coordinate is 'significantly different' from the region we are in. If it is not, then we ignore it. If it is, then we take our next appropriate action. I hope that helps!

* UPDATE *

Here is how we determine that there was enough change to take action:

- (BOOL) radiiAreSignificantlyDifferent:(CLLocationDistance) newRadius oldRadius:(CLLocationDistance)oldRadius {     // radii are in kilometers     return (newRadius > oldRadius + 1.5 || newRadius < oldRadius - 1.5) ? YES : NO; }  - (BOOL) locationsAreSignificantlyDifferent:(CLLocation*) newLocation oldLocation:(CLLocation*)oldLocation {     BOOL different = NO;      if (oldLocation == nil) {         different = YES;     }else{         // have we moved at least a quarter mile?         different = ([newLocation distanceFromLocation:oldLocation] > 400.0)? YES : NO;     }      return different; } 
Read More