[{"content":"","date":null,"permalink":"https://wnagrodzki.eu/","section":"Developer's notepad","summary":"","title":"Developer's notepad"},{"content":"Suppose we would like to dump byte buffer managed by Data instance, which is our case is UTF8 encoded string representation.\n(lldb) po data ▿ 1278 bytes - count : 1278 ▿ pointer : 0x00007ff5fc861800 - pointerValue : 140694480361472 The following command will read1 1278 bytes starting at 0x00007ff5fc861800 and save command output to file at specified path.\n(lldb) memory read --force --count 1278 --outfile /Users/wojtek/data.hexdump 0x00007ff5fc861800 The file format is quite similar to the one used by xxd2. The only difference is that xxd hexdump format does not precede address with 0x.\n% head -n 4 data.hexdump 0x7ff5fc861800: 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f 6c 6f Lorem ipsum dolo 0x7ff5fc861810: 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f 6e 73 r sit amet, cons 0x7ff5fc861820: 65 63 74 65 74 75 72 20 61 64 69 70 69 73 63 69 ectetur adipisci 0x7ff5fc861830: 6e 67 20 65 6c 69 74 2e 20 49 6e 20 61 63 20 63 ng elit. In ac c Conversion is trivial, we need to remove first two bytes in every line.\n% cut -b 3- data.hexdump \u0026gt; data.xxd % head -n 4 data.xxd 7ff5fc861800: 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f 6c 6f Lorem ipsum dolo 7ff5fc861810: 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f 6e 73 r sit amet, cons 7ff5fc861820: 65 63 74 65 74 75 72 20 61 64 69 70 69 73 63 69 ectetur adipisci 7ff5fc861830: 6e 67 20 65 6c 69 74 2e 20 49 6e 20 61 63 20 63 ng elit. In ac c Finally we can proceed with hexdump to binary conversion. Note that we need to tell xxd not to write 0x7ff5fc861800 bytes of zeros before starting to write the actual data.\n% xxd -revert -groupsize 1 --seek -0x7ff5fc861800 data.xxd data.txt Normally, memory read will not read over 1024 bytes of data, use --force option to override this restriction.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCommand line tool for creating hexdumps or do the reverse\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"25 August 2018","permalink":"https://wnagrodzki.eu/posts/dumping-memory-content-to-file-with-lldb/","section":"Posts","summary":"","title":"Dumping memory to file with LLDB"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/lldb/","section":"Tags","summary":"","title":"Lldb"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/xcode/","section":"Tags","summary":"","title":"Xcode"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/bundler/","section":"Tags","summary":"","title":"Bundler"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/gems/","section":"Tags","summary":"","title":"Gems"},{"content":"Installation #Attempt to install bundler will fail as only root have write access to this part of the operating system. Using sudo is not a good choice. macOS depends on gems installed there and we do not want to risk violating system integrity.\n% gem install bundler ERROR: While executing gem ... (Gem::FilePermissionError) You don\u0026#39;t have write permissions for the /Library/Ruby/Gems/2.3.0 directory. Instead, we ought to install bundler in another location, preferably /usr/local/bin directory for non-essential command binaries for all users1. Since this directory does not exist on macOS by default, we will create it manually.\n% ls -l /usr | grep local drwxr-xr-x 3 root wheel 96 Jun 5 20:30 local % sudo mkdir /usr/local/bin Permissions must allow write access for users of admin group. This way they will be able to install software in /usr/local/bin without using sudo.\n% sudo chown `whoami`:admin /usr/local/bin/ % chmod g+w /usr/local/bin/ % ls -l /usr/local/ total 0 drwxrwxr-x 2 wojtek admin 64 Jun 10 17:01 bin Installation succeeded without any file permission errors.\n% gem install bundler --user-install --bindir /usr/local/bin/ Fetching: bundler-1.16.2.gem (100%) Successfully installed bundler-1.16.2 Parsing documentation for bundler-1.16.2 Installing ri documentation for bundler-1.16.2 Done installing documentation for bundler after 7 seconds 1 gem installed Bundle and bundler binaries were installed in /usr/local/bin/, and bundler ruby gem in ~/.gem/ruby/2.3.0/gems/.\n% ls -l /usr/local/bin/ total 16 -rwxr-xr-x 1 wojtek admin 525 Jun 10 17:17 bundle -rwxr-xr-x 1 wojtek admin 526 Jun 10 17:17 bundler % ls -l ~/.gem/ruby/2.3.0/gems/ total 0 drwxr-xr-x 10 wojtek staff 320 Jun 10 17:19 bundler-1.16.2 Usage #Defining dependencies #Specify your dependencies in a Gemfile in your project\u0026rsquo;s root:\nsource \u0026#34;https://rubygems.org\u0026#34; gem \u0026#34;json\u0026#34; gem \u0026#34;jekyll\u0026#34; gem \u0026#34;jekyll-sitemap\u0026#34; gem \u0026#34;jekyll-feed\u0026#34; gem \u0026#34;jekyll-paginate\u0026#34; gem \u0026#34;jekyll-gist\u0026#34; Installing dependencies #bundle install installs the dependencies specified in your Gemfilein the same location as gem install command. We aim keep them in a separate directory, and --path option is going to help us with that.\nAfter installation completes bundler creates hidden .bundle directory with config file inside of it. We will place our dedicated dependencies directory in .bundle as well, so when we finally decide to delete the project the dependencies will get deleted along with it.\n% bundle install --path \u0026#39;.bundle/dependencies\u0026#39; Fetching gem metadata from https://rubygems.org/........... Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Fetching public_suffix 3.0.2 Installing public_suffix 3.0.2 ... Bundle complete! 6 Gemfile dependencies, 35 gems now installed. Bundled gems are installed into `./.bundle/dependencies` Do not forget to commit Gemfile and Gemfile.lock. Add .bundle/dependencies directory to .gitignore. {: .notice}\nExecuting commands #To execute a command in the context of the bundle precede it with bundle exec.\n% bundle exec jekyll serve Bundler will look into config file for the path dependencies are installed. See BUNDLE_PATH in the listing below.\n% cat .bundle/config --- BUNDLE_PATH: \u0026#34;.bundle/dependencies\u0026#34; Updating dependencies #To update dependencies run bundle update.\nFor more information refer to bundler documentation.\n/usr/local/bin purpose is defined by Filesystem Hierarchy Standard\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"11 June 2018","permalink":"https://wnagrodzki.eu/posts/managing-gems-with-bundler-on-macos/","section":"Posts","summary":"","title":"Managing gems with Bundler on macOS"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/ruby/","section":"Tags","summary":"","title":"Ruby"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/http/","section":"Tags","summary":"","title":"HTTP"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/https/","section":"Tags","summary":"","title":"HTTPS"},{"content":"In order to intercept encrypted communication we need to reroute it via proxy, similarly to man in the middle attack. First, we are going to install mitmproxy. There are few ways to install it, but let\u0026rsquo;s do it manually. Download the latest release from GitHub and copy it into /usr/local/bin/ directory.\n% ls ~/Downloads/mitmproxy-2.0.2-osx/ mitmdump mitmproxy mitmweb % cp ~/Downloads/mitmproxy-2.0.2-osx/* /usr/local/bin/ % ls -l /usr/local/bin/ | grep mitm -rwxr-xr-x@ 1 wojtek staff 10477545 Feb 21 20:27 mitmdump -rwxr-xr-x@ 1 wojtek staff 10774075 Feb 21 20:27 mitmproxy -rwxr-xr-x@ 1 wojtek staff 11352485 Feb 21 20:27 mitmweb Run mitmproxy and make it listen on port 8080.\n% mitmproxy --port 8080 Next, redirect all the HTTP and HTTPS traffic to localhost:8080.\nOpen safari and connect to a server via HTTP, for example http://httpbin.org/headers. Request and response will get listed by mimtproxy.\nHit enter to see more details. Navigate between tabs with h and l.\nRoot Certificate Authority on OSX #Any attempt to establish TLS connection will fail as mitmproxy uses certificate that is not trusted.\n% nscurl https://httpbin.org/headers Load failed with error: Error Domain=NSURLErrorDomain Code=-1004 \u0026#34;Could not connect to the server.\u0026#34; UserInfo={NSUnderlyingError=0x7fd749d20ba0 {Error Domain=kCFErrorDomainCFNetwork Code=-1004 \u0026#34;(null)\u0026#34; UserInfo={_kCFStreamErrorCodeKey=61, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://httpbin.org/headers, NSErrorFailingURLKey=https://httpbin.org/headers, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=61, NSLocalizedDescription=Could not connect to the server.} Install mitmproxy root certificate authority with drag and drop mitmproxy-ca.pem file onto your login keychain.\n% ls -l ~/.mitmproxy total 40 -rw-r--r-- 1 wojtek staff 1318 Feb 21 21:06 mitmproxy-ca-cert.cer -rw-r--r-- 1 wojtek staff 2529 Feb 21 21:06 mitmproxy-ca-cert.p12 -rw-r--r-- 1 wojtek staff 1318 Feb 21 21:06 mitmproxy-ca-cert.pem -rw-r--r-- 1 wojtek staff 3022 Feb 21 21:06 mitmproxy-ca.pem -rw-r--r-- 1 wojtek staff 770 Feb 21 21:06 mitmproxy-dhparam.pem Double click on it and select Always Trust in Trust section to trust it. HTTPS communication can be intercepted on your Mac now.\nRoot Certificate Authority on iPhone Simulator #The root certificate authority has to be installed on iPhone Simulator as it manages it\u0026rsquo;s own separate keychain. Drag and drop mitmproxy-ca.pem file on the simulator. After installing it you must specifically enable the root certificate in Settings \u0026gt; General \u0026gt; About \u0026gt; Certificate Trust Settings.\nFor more information refer to Technical Q\u0026amp;A QA1948. ","date":"23 February 2018","permalink":"https://wnagrodzki.eu/posts/intercepting-https-traffic/","section":"Posts","summary":"","title":"Intercepting HTTP(S) traffic"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/iphone-simulator/","section":"Tags","summary":"","title":"IPhone Simulator"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/proxy/","section":"Tags","summary":"","title":"Proxy"},{"content":"I decided to gather all the good practices constituting a convenient project setup, tailored especially for SCRUM, but first let\u0026rsquo;s define what we are aiming for:\nclient receives a product increment by the end of every sprint (production build) it is easy to build a patched version of the increment without including any source code changes introduced after it\u0026rsquo;s shipment scrum master can easily get access to the most current application version that includes all the features built so far (development build) unfinished features are excluded from production and development builds there are separate applications for debug, development, production and app store builds it is easy to distinguish one from another no data is shared between them continuous integration server is not needed for building production and development builds formula that produces version and build number must give the same results independently of computer it runs on it is easy to relate version and build number to the commit application was build from Source code repository #There is already a git branching model to help us meet first four points. If you are not familiar with GitFlow I would recommend reading this tutorial.\nFollowing image shows an example of a repository using GitFlow. Feature, release and hotfix branches were not removed to make the history easier to follow.\nOnce we remove unnecessary branches, repository gets cleaner and released version tags more visible.\nYou can also use a filter to get even clearer view on the develop or master history.\nBuild configurations #Points 5 and 6 can be realized by adding new build configurations (they should be duplicated from Release build configuration).\nDevelopment - built from develop branch, signed with enterprise provisioning profile. Production - built from master branch, signed with enterprise provisioning profile. App Store - built from master branch, signed with App Store provisioning profile. Make sure all projects in workspace and sub-projects share the same set of build configurations. To get separate application for each build configuration we need to provide distincs bundle identifiers.\nApplication name is a bit more complex. First we add user-defined setting BUNDLE_NAME.\nSecond we place it in Info.plist file under CFBundleName entry.\nDespite Xcode uses $(PRODUCT_NAME) for CFBundleName default value it is better not to change PRODUCT_NAME value per build configuration. That would result in changing the name of the binary the target produces. See EXECUTABLE_NAME documentation for more information. Application icons can also be build configuration dependent. After providing app icon sets for default asset catalog put their names in the following build setting.\nSigning #Make sure you are logged in with your Apple ID to development portal via Xcode and you have downloaded/redownloaded all manual profiles before proceeding.\nAutomatic signing is convenient to use with Debug build configuration. Xcode will generate certificate and provisioning profile for each developer working on the project. We have to use manual signing for the rest: Development, Production and AppStore. Once Code Signing Style is set up we can adjust Development Team and Provisioning Profile sections.\nA few advises:\nEnterprise certificates come in pairs. If you happen to have both of them in Keychain Access and your application\u0026rsquo;s provisioning profile is signed with the older one, the project will fail to build. There are two ways to work around that issue, either resign the provisioning profile with newer certificate or remove it from Keychain Access. It is not possible to resign application with provisioning profile and certificate that come from another team. Keep enterprise certificate and private key in a separate keychain file, it is easier to manage them in this way. Automatic Build and Version Numbers #Each release on master branch should be tagged according to semantic versioning standard. The tag is going be used also as application version number (CFBundleShortVersionString). Build number (CFBundleVersion) will be a product of concatenating three integer values:\nnumber of commits - this value will increment with every new commit on active branch commit short SHA in decimal format - will make it easy to find the commit application was build from commit build number - will be incremented in case more then one version is built from a commit For example, for commit f4ab753 we would get 0.2.0 for version number and 16.256554835.0 for build number.\nThe following script updates version and build number automatically without modifying your working copy.\n#!/bin/bash #====================================================================================== # FILE: update_plist.sh # # USAGE: update_plist.sh [commit build number] # # DESCRIPTION: Determines version and build number for to the currently # checked out commit and updates them in iOS application plist file # located in BUILT_PRODUCTS_DIR/INFOPLIST_PATH directory. # # REQUIREMENTS: BUILT_PRODUCTS_DIR and INFOPLIST_PATH environment variables. # See `PLIST_PATH` variable below. # # AUTHOR: Wojciech Nagrodzki # REVISION: 11 March 2018 #====================================================================================== prepare_repository_data_constants() { readonly LATEST_TAG_NAME=$(git describe --tags $(git rev-list --tags --max-count=1)) readonly NUMBER_OF_COMMITS=$(git rev-list HEAD --count) local COMMIT_SHA_PRECISION=7 local LAST_COMMIT_SHA_HEX=$(git rev-parse --short=$COMMIT_SHA_PRECISION HEAD) readonly LAST_COMMIT_SHA_DEC=$((0x$LAST_COMMIT_SHA_HEX)) echo \u0026#34;LATEST_TAG_NAME=$LATEST_TAG_NAME\u0026#34; echo \u0026#34;NUMBER_OF_COMMITS=$NUMBER_OF_COMMITS\u0026#34; echo \u0026#34;LAST_COMMIT_SHA_HEX=$LAST_COMMIT_SHA_HEX\u0026#34; echo \u0026#34;LAST_COMMIT_SHA_DEC=$LAST_COMMIT_SHA_DEC\u0026#34; } update_version_number() { echo \u0026#34;Set $LATEST_TAG_NAME for CFBundleShortVersionString\u0026#34; $PLISTBUDDY -c \u0026#34;Set :CFBundleShortVersionString $LATEST_TAG_NAME\u0026#34; \u0026#34;$PLIST_PATH\u0026#34; } update_build_number() { local BUILD_NUMBER=\u0026#34;$NUMBER_OF_COMMITS.$LAST_COMMIT_SHA_DEC.$COMMIT_BUILD_NUMBER\u0026#34; echo \u0026#34;Set $BUILD_NUMBER for CFBundleVersion\u0026#34; $PLISTBUDDY -c \u0026#34;Set :CFBundleVersion $BUILD_NUMBER\u0026#34; \u0026#34;$PLIST_PATH\u0026#34; } readonly COMMIT_BUILD_NUMBER=${1:-0} readonly PLIST_PATH=\u0026#34;$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\u0026#34; readonly PLISTBUDDY=\u0026#34;/usr/libexec/PlistBuddy\u0026#34; prepare_repository_data_constants echo \u0026#34;Updating version and build number in plist file: $PLIST_PATH\u0026#34; update_version_number update_build_number It should be executed in target build phase.\nOfficial documentation is not consistent in how CFBundleShortVersionString and CFBundleVersion should look like. Basing on my experience both numbers should be incremented with every new version, failure to do so will result in rejecting the binary by iTunes Connect portal. See Technical Note TN2420 for more details. Building #The fore-mentioned setup allows us to use Xcode archive command to build application with any build configuration we choose. This way we can build and deliver the application increment even if CI system breaks.\nIt is also easy to integrate the setup with Xcode bots, Fastlane, Jenkins and Travis.\n","date":"17 December 2017","permalink":"https://wnagrodzki.eu/posts/successful-xcode-project-setup/","section":"Posts","summary":"","title":"Successful Xcode Project Setup"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/caanimation/","section":"Tags","summary":"","title":"CAAnimation"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/calayer/","section":"Tags","summary":"","title":"CALayer"},{"content":"You may find yourself in a situation when iOS removes animations automatically. It occurs in two cases:\nwhen an application goes into the background, all animations are removed from all layers when a view is removed from the window, all animations are removed from it\u0026rsquo;s layer tree To counteract the system we need to pause the animations and copy them over to a safe place. Next, when the right moment comes, we need to add them back to the layers and resume.\nPausing and resuming animations of a layer tree #Technical Q\u0026amp;A QA1673 shows us how to stop the passage of time in a layer tree.\nextension CALayer { /// Pauses animations in layer tree. fileprivate func pause() { let pausedTime = convertTime(CACurrentMediaTime(), from: nil) speed = 0.0; timeOffset = pausedTime; } /// Resumes animations in layer tree. fileprivate func resume() { let pausedTime = timeOffset; speed = 1.0; timeOffset = 0.0; beginTime = 0.0; let timeSincePause = convertTime(CACurrentMediaTime(), from: nil) - pausedTime; beginTime = timeSincePause; } } Storing and restoring animations #Beside animation object we need to store two additional pieces of information, reference to CALayer the animation was added to and the key that identifies it. This way we will know where to restore the animations and under which keys. A convenient way to model it would be [key : animation] dictionary on CALayer.\nextension CALayer { private static let association = ObjectAssociation\u0026lt;NSDictionary\u0026gt;() private var animationsStorage: [String: CAAnimation] { get { return CALayer.association[self] as? [String : CAAnimation] ?? [:] } set { CALayer.association[self] = newValue as NSDictionary } } ObjectAssociation is a convenient way to utilize associated object objective-c feature. {: .notice}\nHaving the storage place we can finish storeAnimations and restoreAnimations methods with ease. Note that they are both recursive functions.\n/// Returns a dictionary of copies of animations currently attached to the layer along with their\u0026#39;s keys. private var animationsForKeys: [String: CAAnimation] { guard let keys = animationKeys() else { return [:] } return keys.reduce([:], { var result = $0 let key = $1 result[key] = (animation(forKey: key)!.copy() as! CAAnimation) return result }) } /// Pauses the layer tree and stores it\u0026#39;s animations. func storeAnimations() { pause() depositAnimations() } /// Resumes the layer tree and restores it\u0026#39;s animations. func restoreAnimations() { withdrawAnimations() resume() } private func depositAnimations() { animationsStorage = animationsForKeys sublayers?.forEach { $0.depositAnimations() } } private func withdrawAnimations() { sublayers?.forEach { $0.withdrawAnimations() } animationsStorage.forEach { add($0.value, forKey: $0.key) } animationsStorage = [:] } } Storing and restoring animations automatically #So far we were able to use only extensions to add functionality to existing classes, but in order to be notified when a view is added to/removed from window we are forced to subclassing (at least I could not find a way to achieve this, window property on UIView is not KVO-compliant).\nclass AnimationPreservingView: UIView { override init(frame: CGRect) { super.init(frame: frame) registerForNotifications() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) registerForNotifications() } override func willMove(toWindow newWindow: UIWindow?) { super.willMove(toWindow: newWindow) if newWindow == nil { layer.storeAnimations() } } override func didMoveToWindow() { super.didMoveToWindow() if window != nil { layer.restoreAnimations() } } } extension AnimationPreservingView { fileprivate func registerForNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(AnimationPreservingView.applicationWillResignActive), name: .UIApplicationWillResignActive, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(AnimationPreservingView.applicationDidBecomeActive), name: .UIApplicationDidBecomeActive, object: nil) } @objc private func applicationWillResignActive() { guard window != nil else { return } layer.storeAnimations() } @objc private func applicationDidBecomeActive() { guard window != nil else { return } layer.restoreAnimations() } } Once your views are embedded in AnimationPreservingView their animations will be safe.\nNavigate here to see the full project.\n","date":"9 April 2017","permalink":"https://wnagrodzki.eu/posts/restoring-animations/","section":"Posts","summary":"","title":"Restoring Animations"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/swift/","section":"Tags","summary":"","title":"Swift"},{"content":"Associated objects still prove to be useful in Swift, but burdened with quite a lot of boilerplate. Fortunately it can be factored out to a helper class:\npublic final class ObjectAssociation\u0026lt;T: AnyObject\u0026gt; { private let policy: objc_AssociationPolicy /// - Parameter policy: An association policy that will be used when linking objects. public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { self.policy = policy } /// Accesses associated object. /// - Parameter index: An object whose associated object is to be accessed. public subscript(index: AnyObject) -\u0026gt; T? { get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? } set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) } } } In result we receive succinct and readable piece of code.\nextension SomeType { private static let association = ObjectAssociation\u0026lt;NSObject\u0026gt;() var simulatedProperty: NSObject? { get { return SomeType.association[self] } set { SomeType.association[self] = newValue } } } ","date":"26 March 2017","permalink":"https://wnagrodzki.eu/posts/associated-objects-in-swift/","section":"Posts","summary":"","title":"Associated Objects in Swift"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/objective-c/","section":"Tags","summary":"","title":"Objective-C"},{"content":"iOS is missing drag \u0026amp; drop gesture recognizer that would require user to hold his finger for a moment to begin similarly to UILongPressGestureRecognizer. Animation below presents how the gesture is going to look.\nAPI Design #To be able to move a view gesture recognizer must deliver information about the distance and direction the gesture moves, namely - translation.\nReporting current translation can be accomplished in two ways. First approach would be to provide delta translation from the last time the translation was reported. Unfortunately this can easily lead to precision loss as numerical errors would add up with each delta delivery (view.center += deltaTranslation). In the second approach total translation would be provided instead, thus we would avoid the problem (view.center = viewInitialPosition + totalTranslation).\nWe also need to take into consideration that views may have various transformations applied to them (you can get the idea here). The same gesture could traverse different coordinate systems, resulting in various translation values.\nNow it is clear that our translation function should take view as a parameter.\n/// The total translation of the drag gesture in the coordinate system of the specified view. /// - parameters: /// - view: The view in whose coordinate system the translation of the drag gesture should be computed. Pass `nil` to indicate window. func translation(in view: UIView?) -\u0026gt; CGPoint As you probably have noticed this API has a drawback, each time the gesture begins client has to store initial view position in order to calculate the new position (view.center = viewInitialPosition + totalTranslation). This inconvenience can be avoided if we enable the gesture recognizer to modify it\u0026rsquo;s translation.\n/// Sets the translation value in the coordinate system of the specified view. /// - parameters: /// - translation: A point that identifies the new translation value. /// - view: A view in whose coordinate system the translation is to occur. Pass `nil` to indicate window. func setTranslation(_ translation: CGPoint, in view: UIView?) Provided that function, client can assign the view position as translation when gesture begins and avoid managing view initial position variable at all.\n@objc private func handleGesture(gestureRecognizer: DragGestureRecognizer) { switch gestureRecognizer.state { case .began: gestureRecognizer.setTranslation(draggableView.center, in: view) case .changed: draggableView.center = gestureRecognizer.translation(in: view) case .cancelled, .failed, .ended, .possible: break } } Implementation #Since UILongPressGestureRecognizer already knows how to wait for a moment before the gesture begins we can use it as our base class.\nIn order to be able to get and set total translation we will need two properties:\nprivate var initialTouchLocationInScreenFixedCoordinateSpace: CGPoint? private var initialTouchLocationsInViews = [UIView: CGPoint]() First one will store initial touch location in screen\u0026rsquo;s fixed coordinate space, the location can be easily converted to any view\u0026rsquo;s coordinate space. Second will store altered initial touch locations per view, this will allow the gesture recognizer to set custom translations per view.\noverride var state: UIGestureRecognizerState { didSet { switch state { case .began: initialTouchLocationInScreenFixedCoordinateSpace = location(in: nil) case .ended, .cancelled, .failed: initialTouchLocationInScreenFixedCoordinateSpace = nil initialTouchLocationsInViews = [:] case .possible, .changed: break } } } The fore-mentioned code listing reports an error unless the following header is imported.\nimport UIKit.UIGestureRecognizerSubclass Once we have the properties in place we can provide the function to return translation. The idea is quite trivial, we get initial and current touch locations and return the difference. Please mind that initial touch location may be overridden by a value stored in initialTouchLocationsInViews.\nfunc translation(in view: UIView?) -\u0026gt; CGPoint { // not attached to a view or outside window guard let window = self.view?.window else { return CGPoint() } // gesture not in progress guard let initialTouchLocationInScreenFixedCoordinateSpace = initialTouchLocationInScreenFixedCoordinateSpace else { return CGPoint() } let initialLocation: CGPoint let currentLocation: CGPoint if let view = view { initialLocation = initialTouchLocationsInViews[view] ?? window.screen.fixedCoordinateSpace.convert(initialTouchLocationInScreenFixedCoordinateSpace, to: view) currentLocation = location(in: view) } else { initialLocation = initialTouchLocationInScreenFixedCoordinateSpace currentLocation = location(in: nil) } return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y) } Assigning a new translation boils down to storing initial touch location in the initialTouchLocationsInViews dictionary.\nfunc setTranslation(_ translation: CGPoint, in view: UIView?) { // not attached to a view or outside window guard let window = self.view?.window else { return } // gesture not in progress guard let _ = initialTouchLocationInWindow else { return } let inView = view ?? window let currentLocation = location(in: inView) let initialLocation = CGPoint(x: currentLocation.x - translation.x, y: currentLocation.y - translation.y) initialTouchLocationsInViews[inView] = initialLocation } Navigate here to see the full project.\n14 March 2019: Updated to include newest bug fixes. #","date":"12 September 2016","permalink":"https://wnagrodzki.eu/posts/drag-gesture-recognizer/","section":"Posts","summary":"","title":"Drag Gesture Recognizer"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/gesture-recognizer/","section":"Tags","summary":"","title":"Gesture Recognizer"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/core-data/","section":"Tags","summary":"","title":"Core Data"},{"content":"Core Data brings a bit of Objective-C legacy into Swift world making our work with managed objects not as safely typed as we wish it to be.\nMaking a better use of scalar types #Xcode provides us with two ways of dealing with the following group of primitive values. We can use them as NSNumber?:\nclass ManagedObject: NSManagedObject { @NSManaged public var double: NSNumber? @NSManaged public var float: NSNumber? @NSManaged public var boolean: NSNumber? @NSManaged public var integer16: NSNumber? @NSManaged public var integer32: NSNumber? @NSManaged public var integer64: NSNumber? } or scalar types:\nclass ManagedObject: NSManagedObject { @NSManaged public var double: Double @NSManaged public var float: Float @NSManaged public var bool: Bool @NSManaged public var integer16: Int16 @NSManaged public var integer32: Int32 @NSManaged public var integer64: Int64 } The latter is more convenient to use in Swift, however it is not ideal:\nattributes cannot be declared as optionals - which makes perfect sense in some cases attributes will return zero before they are assigned another value - this may be unexpected sometimes Both issues can be resolved by introducing two layers of properties, private for Core Data use, and public (or internal) providing convenient API.\nclass Movie: NSManagedObject { @NSManaged private var cd_userRating: NSNumber? @NSManaged private var cd_duration: NSNumber? } extension Movie { // nil represents no rating at all var userRating: Double? { get { return cd_userRating?.doubleValue } set { cd_userRating = userRating.map { NSNumber(value:$0) }} } // movie duration in minutes var duration: Int32 { get { return cd_duration!.int32Value } set { cd_duration = NSNumber(value:length) } } } Please note that the second layer is more descriptive about the data model than the first one.\nStrongly typed relationships #Auto-generated relationships have a few inconveniences as well:\nclass ManagedObject: NSManagedObject { @NSManaged public var relationToOne: ManagedObject? @NSManaged public var relationToMany: NSSet? } to many relationships are represented by NSSet? instead of Set\u0026lt;T\u0026gt; - strongly typed API fits nicer into Swift to many relationships return nil instead of empty set if there are no related objects - having empty set instead of optional one yields cleaner code to one relationships are optional by default - this may have no sense in some cases We can apply similar pattern to deal with them:\nclass Folder: NSManagedObject { @NSManaged private var cd_owner: User? @NSManaged private var cd_files: NSSet? } extension Folder { var owner: User { get { return cd_owner! } set { cd_owner = owner } } var files: Set\u0026lt;File\u0026gt; { get { return (cd_files ?? []) as! Set\u0026lt;File\u0026gt; } set { cd_files = files as NSSet } } } Readonly properties #After improving typing for attributes and relationships we can take one step further and make some of them readonly. In that case we may have to provide initializer for assigning initial values.\nclass Message: NSManagedObject { @NSManaged private var cd_sender: Sender? } extension Message { var sender: Sender { return cd_sender! } } extension Message { convenience init(context: NSManagedObjectContext, sender: Sender) { self.init(context: context) cd_sender = sender } } The foregoing code listing uses init(context moc: NSManagedObjectContext) introduced in iOS 10. If we need to support older systems we can use the following implementation.\nextension Message { static func makeInstance(sender: Sender, in context: NSManagedObjectContext) -\u0026gt; Message { let message = Message.makeInstance(in: context) message.cd_sender = sender return message } } @objc protocol NSManagedObjectConveniences {} extension NSManagedObjectConveniences { static func makeInstance(in context: NSManagedObjectContext) -\u0026gt; Self { let entityName = String(describing: Self.self) let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context)! let managedObject = NSManagedObject(entity: entityDescription, insertInto: context) as! Self return managedObject } } extension NSManagedObject: NSManagedObjectConveniences {} ","date":"27 August 2016","permalink":"https://wnagrodzki.eu/posts/using-core-data-in-swift/","section":"Posts","summary":"","title":"Using Core Data in Swift"},{"content":"Assumptions #I wrote quite a few apps utilising synchronization with CoreData since 2010 and it has never been easy. The architecture I am going to present is trying to meet the following requirements:\nsynchronization should work on a background thread to minimise user interface lagging synchronization should not interfere with user actions, e.g. a contact should not be updated if user is editing it it should be possible to run synchronizations at various priorities the architecture should be modular making it easy to extend and modify the architecture should not produce merge conflicts between managed object contexts - NSMergeConflict Performant core data stack #Two independent managed object contexts should be used to achieve high performance. The first one (main context) will be working with mainQueueConcurrencyType and the second (background context) with privateQueueConcurrencyType. To understand that decision you should read the following articles by Florian Kugler: Concurrent Core Data Stacks – Performance Shootout, Backstage with Nested Managed Object Contexts\nDraft of the architecture # Data Import Synchronizer manages Data Import Operation Queue. The queue can execute only one Data Import Operation at a time. Each operation saves Background MOC, thereby saving changes to Persistent Store. Once the save is done Background MOC posts NSManagedObjectContextDidSave. The notification is received by Data Import Synchronizer which calls mergeChanges(fromContextDidSave:) on Main MOC, in result keeping both context in sync.\nThere are a few things to keep in mind here.\nFirst, Core Data locks Persistent Store Coordinator for the time save operation is performed by Background MOC. It means that Main MOC will have to wait if it queries Persistent Store Coordinator for some data. It is wise to keep Data Import Operations relatively small.\nSecond, NSManagedObjectContextDidSave is not posted synchronously after save() method call. Technically there is a chance to fetch some managed objects to Main MOC, after Background MOC save() is executed, and before mergeChanges(fromContextDidSave:) is performed. It might result in unexpected app behaviour or even crashes.\nThird, making changes in Main MOC may result in conflicts with Background MOC. There is no good support for user edits, and this is the issue we are going to resolve next.\nAdding support for user edits # Managed objets altered by a user should appear in Main MOC and persistent store. With Data Import Operations executing in the background mergeChanges(fromContextDidSave:) can be called at any moment, which can result in conflicts. This problem can be avoided for example by pausing Data Import Operation Queue for the time user is performing edits, and this is an approach we are going to focus on.\nHigh level description is the mechanics looks as follows:\nClient asks Data Import Synchronizer for transaction Data Import Synchronizer adds Data Import Transaction operation to Data Import Operation Queue with highest possible priority Once the operation starts it informs the client that transaction has started Data Import Transaction stays in the queue until it is finalised The transaction is delivered asynchronously Client allows user to perform edits Changes are saved to persistent store Client finalises the transaction Data Import Transaction calls reset() on Background MOC Data Import Transaction is marked as finished and leaves the queue Data Import Operations continue their work As you have probably noticed there are two caveats of this approach:\none, user need to wait until currently executing Data Import Operation finishes. two, instead of merging changes form Main MOC to Background MOC, the latter is reset. I leaned towards this approach as merging changes was not trivial to implement. The solution worked really well in practice.\nuser interface lagging was minimal synchronization stopped when user performed edits it was possible to assign various priorities to Data Import Operations that needed it it was easy to extend synchronization by adding more Data Import Operations merge conflicts never occured ","date":"21 February 2016","permalink":"https://wnagrodzki.eu/posts/designing-synchronisation-with-core-data/","section":"Posts","summary":"","title":"Designing Synchronization with Core Data"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/synchronization/","section":"Tags","summary":"","title":"Synchronization"},{"content":"This LaTeX template will allow you to create elegant documents with:\nautomatic table of content creation custom sections, subsections and sub-subsections code listings with syntax highlighting for Swift and Objective-C styled rectangles with tips or important information You can download it here.\n","date":"17 September 2015","permalink":"https://wnagrodzki.eu/posts/apple-style-latex-template/","section":"Posts","summary":"","title":"Apple Documentation Style LaTeX Template"},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/tags/latex/","section":"Tags","summary":"","title":"LaTeX"},{"content":"Professional experience #Trifork GmbH #First iOS developer hired by Trifork in Poland. Successfully established and led the iOS team. Served as team leader, trainer, and developer.\nMatch 2025 - Ongoing Iris Messenger\nSelf-hosted chat application for iOS, built using the XMPP protocol.\nSwift, SwiftUI, UIKit, XMPPFramework, MongooseIM\nDec 2024 - Apr 2025 USAA Water Mitigation\nVirtual reality application developed for VisionOS to assist instructors during water mitigation training sessions.\nSwift, SwiftUI, VisionOS\nAug 2022 - Nov 2024 Hollywoodbets\nResponsible for porting ReactJS web betting application to iOS and Android native apps.\nReactNative, TypeScript, JavaScript, jest, Redux, Redux-Saga, Microsoft Azure, Microsoft App Center\nAug 2019 - Nov 2021 Supertrends\nA mobile application designed to track, analyze, and predict global trends, innovations, and technologies, empowering users to make well-informed business decisions.\nSwift, UIKit, UIKit Dynamics, Core Graphics, Core Animation, SceneKit, Apollo iOS, GraphQL\nDec 2017 - Oct 2018 Systematic Columna Cura\nThe electronic care record that ensures efficient documentation and optimal task management of community care services.\nFHIR, Swift, UIKit, Core Graphics, Core Animation\nJul 2017 - Ongoing TFPDFKit\nSignDoc\u0026rsquo;s PDF handling is built on top of this foundation.\nPDFKit\nJul 2016 - Nov 2018 GOTO Guide\nGOTO Guide provides easy access to features such as real-time session voting, live question submission, and the ability to comment on your vote.\nSwift, Objective-C, UIKit, Core Data, Core Graphics, Core Animation\nMay 2016 - Ongoing BI Reporting\nA business intelligence tool used to collect financial data in spreadsheet like manner and visualize it through charts.\nSwift, UIKit, Core Data, Core Graphics, Core Animation, SwiftyJSON, Decodable, Encrypted Core Data SQLite Store, Locksmith\nMay 2016 - Ongoing SignDoc\nPDF document singing solution.\nSwift, UIKit, Core Data, Core Graphics, Core Animation, Vapor, Docker\nFeb 2016 - Mar 2017 Expat Tracker\nExpat Tracker keeps track of the countries you visit and provides reports for your tax return.\nSwift, Objective-C, UIKit, Core Data, Core Graphics, Core Animation, Core Location, SwiftyJSON\nOct 2015 - Mar 2016 Danske Bank WeShare\nWeShare is an app designed to simplify expense tracking for groups during trips, parties, or shared activities. It allows users to share costs, send photos, and chat—all within a single platform.\nSwift, Objective-C, UIKit, Core Data, Core Graphics, Core Animation, Core Location, SwiftyJSON\nAug 2015 - Oct 2016 Hemonto\nMobile investment reporting applciation. I was responsible for developing specific components of the application and providing ongoing client support.\nSwift, UIKit, Core Graphics, Core Animation, SwiftyJSON\nNov 2014 - Dec 2015 Credit Suisse Zürich Strategy Review\nDue to an NDA I am not allowed to discuss the project.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation\nSep 2013 - Jul 2015 Credit Suisse Zürich Portfolio Review\nDue to an NDA I am not allowed to discuss the project.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation\nSep 2012 - Oct 2014 Credit Suisse Zürich Strategy Navigator\nDue to an NDA I am not allowed to discuss the project.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation\nAug 2012 - Sep 2014 Woodman Wealth Management AG\nPrimarily responsible for bug fixing and making minor adjustments to the application.\nSwift, Objective-C, UIKit, Core Data, Core Graphics, Core Animation, Git, Jenkins, Google Analytics\nJul 2014 - Jul 2014 MDI\nDeveloped a prototype of a chat client tailored to the needs of very busy professionals, such as CEOs and COOs.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation, Core Location\nFeb 2014 - Jul 2014 Credit Suisse Zürich iHypo\nDue to an NDA I am not allowed to discuss the project.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation\nApr 2013 - Feb 2014 KMD SmartCARE\nKMD SmartCARE is a mobile application designed for nurses and homecare workers in Danish municipalities. The solution enables healthcare professionals to deliver home care services efficiently, with all necessary patient data accessible on the go. With SmartCARE, users can view and update patient documentation and communicate seamlessly with colleagues, doctors, pharmacies, and hospitals.\nFeb 2013 - Apr 2013 eMobility Secure Data\nWe were responsible for developing a component that provided transparent 256-bit AES full-database encryption for Apple’s Core Data.\nFeb 2013 - Feb 2013 Ongoing QCon London Guide\nResponsible for rebranding the Trifork Event App for QCon London 2014, testing it, and releasing it on the App Store.\nJan 2013 - Mar 2014 Trifork Event App\nResponsible for application maintenance, bug fixes, and implementing new features, including session voting and the ability for users to submit questions to speakers during talks.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation\nNov 2012 - Aug 2013 Radiometer - Selling Against Competitors\nA mobile application developed to support users in selling medical equipment efficiently.\nSep 2012 - Sep 2013 Proofer\nA mobile app that provides a platform for users to register their products, enabling them to easily file insurance claims when needed.\nSep 2012 - Oct 2012 Skriveormen\nResponsible for localizing the application into Danish and implementing a feature that allows users to switch the application language on the fly.\nJan 2012 - Oct 2012 Top Ten Garage\nResponsible for developing the game engine that allowed players to collect cards and compete against either the computer or other players, using Apple\u0026rsquo;s Game Center or Bluetooth.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation, GameKit, AFNetworking, JSONKit, Twitter, Facebook, Google Analytics, In-App Purchases\nOct 2011 - Jul 2012 Credit Suisse Zürich\nGlobal Research Mobile application. Due to an NDA I am not allowed to discuss the project.\nObjective-C, UIKit, Core Data, Core Graphics, Core Animation, XML, HTML, CSS, Mercurial, Jenkins\nGameLab Innovation Center #Prototyped and developed game engines, collaborating closely with both 2D and 3D artists.\nAug 2011 - Oct 2011 Last Temple 2\nReleased version 2.0 of the game engine. Project continuation was transitioned to another developer.\nObjective-C, Cocos2D, OpenGL, Tiled, Glyph Designer\nFeb 2011 - Jul 2011 The Dungeon Saga, The Dungeon Saga HD\nEnhanced the Last Temple engine to support a dungeon crawler game featuring player-versus-computer combat.\nObjective-C, Cocos2D, OpenGL, Tiled, Glyph Designer\nJul 2010 - Jan 2011 Last Temple, Last Temple HD\nBuilt the game engine and implemented tools to simplify stage creation for level designers.\nObjective-C, Cocos2D, OpenGL, Tiled, Glyph Designer\nFreelancer # May 2010 - Sep 2011 Trendy\nA macOS application for managing stocks in long-term investment strategies.\nObjective-C, AppKit, Core Data, Amazon S3, Sparkle, Growl Education # AGH University of Krakow\n2005 - 2010 - Master of Engineering (MEng), Automatics and Robotics with major in Computer Science in Control and Management. Rector scholarship for the best students, 10/2008 - 06/2009 Grade: Better Than Good (4.5 / 5.0) ","date":null,"permalink":"https://wnagrodzki.eu/cv/","section":"Developer's notepad","summary":"","title":""},{"content":"","date":null,"permalink":"https://wnagrodzki.eu/categories/","section":"Categories","summary":"","title":"Categories"}]