Today I am about to integrate a library that does not support Cocoapods yet. It would be cumbersome to do it manually, because you have to configure xcconfig, framework search path, assets, and these steps are not well documented.
You can do this with custom podspec. In my case, I need to install PinchSDK. First, declare a PinchSDK.podspec in your project folder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Pod::Spec.new do|s| s.name = "PinchSDK" s.version = "1.9.14" s.summary = "Pinch samler dessuten inn data hver gang en mobilapplikasjon oppdager en Pinch-beacon." s.homepage = "https://bitbucket.org/fluxloop/pinch.installpackage" s.source = { :http => "https://bitbucket.org/fluxloop/pinch.installpackage/raw/master/iOS/PinchSDK.zip" } s.authors = 'Fluxloop' s.license = { type:'MIT' } s.platform = :ios, '8.0' s.requires_arc = true s.resource = 'PinchSDK/Pinch.bundle' s.vendored_frameworks = 'PinchSDK/PinchLibrary.framework' s.xcconfig = { 'OTHER_LDFLAGS': '-ObjC' } s.public_header_files = 'PinchSDK/PinchLibrary.framework/Headers/PinchLibrary.h' s.source_files = 'PinchSDK/PinchLibrary.framework/Headers/PinchLibrary.h' end
Then, in your Podfile, you can point to this podspec
1
pod 'PinchSDK', podspec:'PinchSDK.podspec'
Finally, since this PinchSDK uses objc, you need to declare it in your bridging header
Today I was migrating Imaginary to Swift 4. But I get
1 2 3 4 5
- ERROR | [OSX] xcodebuild: Returned an unsuccessful exit code. - ERROR | [OSX] xcodebuild: Cache/Source/Mac/NSImage+Extensions.swift:32:64: error: 'png' has been renamed to 'PNG' - NOTE | [OSX] xcodebuild: AppKit.NSBitmapImageRep:57:27: note: 'png' was introduced in Swift 4 - ERROR | [OSX] xcodebuild: Cache/Source/Mac/NSImage+Extensions.swift:32:71: error: 'jpeg' has been renamed to 'JPEG' - NOTE | [OSX] xcodebuild: AppKit.NSBitmapImageRep:52:27: note: 'jpeg' was introduced in Swift 4
It turns out that the .swift-version is still showing 3.0. Change it to 4.0 fixes the issue. The .swift-version is a hint to specify which Swift version should be used for a pod https://github.com/CocoaPods/CocoaPods/pull/5841
Ikigai (生き甲斐, pronounced [ikiɡai]) is a Japanese concept that means “a reason for being.” It is similar to the French phrase Raison d’être. Everyone, according to Japanese culture, has an ikigai. Finding it requires a deep and often lengthy search of self. Such a search is important to the cultural belief that discovering one’s ikigai brings satisfaction and meaning to life.[1]
The term ikigai compounds two Japanese words: iki (wikt:生き) meaning “life; alive” and kai (甲斐) “(an) effect; (a) result; (a) fruit; (a) worth; (a) use; (a) benefit; (no, little) avail” (sequentially voiced as gai) “a reason for living [being alive]; a meaning for [to] life; what [something that] makes life worth living; a raison d’etre”.[3]
I use Twitter a lot, mostly to follow people I like. They tweet cool things about tech and life. I learned a lot.
Please don’t show me the evil sides of the world ~ Michael Learn To Rock - How Many Hours
But there’s also bad side of the story. I see many retweets of people saying bad things about others, mostly in form of Ad Hominem
Attacking the person making the argument, rather than the argument itself, when the attack on the person is completely irrelevant to the argument the person is making.
An argumentum ad hominem is any kind of argument that criticizes an idea by pointing something out about the people who hold the idea rather than directly addressing the merits of the idea. ‘’Ad hominem’’ is Latin for “directed toward the man (as opposed to the issue at hand)”. An alternative expression is “playing the man and not the ball”.
Most of these people have the Twitter verified badge. They complain that they have so many followers while they themselves follow hundreds of thousands. They say bad things about others’ hair style and appearance while actively supporting equality. They argue who owns the original gif. They follow one person just to be the first to insult them.
The blue verified badge on Twitter lets people know that an account of public interest is authentic.
Blowing out someone else’s candle doesn’t make yours shine any brighter ~ Anonymous
The only thing I can do is I don't like this tweet 😞
Use try catch for read, Result for write, together with concurrent queue. Use sync function for read to block current thread, while using async function with barrier flag for write to return to current queue. This is good for when multiple reads is preferred when there is no write. When write with barrier comes into the queue, other operations must wait.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
funcgetUser(id: String)throws -> User { var user: User! try concurrentQueue.sync { user = try storage.getUser(id) }
Before we could use dispatch_apply to submits a block to a dispatch queue for multiple invocations. Starting with Swift, the equivalence is concurrentPerform
1 2 3 4
DispatchQueue.concurrentPerform(iterations: 1000) { index in let last = array.last ?? 0 array.append(last + 1) }
Using spec testing framework like Quick is nice, which enables BDD style.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
describe("the 'Documentation' directory") { it("has everything you need to get started") { let sections = Directory("Documentation").sections expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups")) expect(sections).to(contain("Installing Quick")) }
context("if it doesn't have what you're looking for") { it("needs to be updated") { let you = You(awesome: true) expect{you.submittedAnIssue}.toEventually(beTruthy()) } } }
But in case you don’t want additional frameworks, and want to live closer to Apple SDKs as much as possible, here are few tips.
funcbuttonDidPressindex: Int) { let initialIndex = tabBarView.selectedIndex let wholeAppContentView = updateWholeAppContentView() view.addSubview(wholeAppContentView) } }
The delegate method does not look right, as it’s hard to tell between required delegate method, or just instance method. Also it lacks a subject. I like this post API Design, you can read section Rule 19: Always say who’s talking
This is a simple rule, and an equally simple mistake to make. In your delegate methods, always pass the sender as a parameter. Always. Even for singletons. Even for things you cannot conceive would ever be used more than once simultaneously. No exceptions.
So I refactor the delegate, and conform to it.
1 2 3 4 5 6 7 8
extensionMainController: TabBarViewDelegate{
functabBarView(_ view: TabBarView, buttonDidPress index: Int) { let initialIndex = tabBarView.selectedIndex let wholeAppContentView = updateWholeAppContentView() view.addSubview(wholeAppContentView) // This is the culprit ⚠️ } }
Even with just 1 line change in MainController.swift, the whole UI breaks, as all the views were added to the tab bar. Strange 😡 .
It didn’t take long until I remember that parameter takes precedence over instance property if they have same name. So in this case, the compiler, without warning, assume you’re dealing with view from TabBarView ⚠️
That’s why you often use self to disambiguate.
1 2 3 4 5 6 7 8 9
structUser: Codable, Equatable{ let firstName: String let lastName: String
Note that you need to include filename + extension to enable automatic language markup
Click Add file to add more files
Cloning the gist
If you’ve enabled 2 factor authentication, you need to use personal acccess token with https, or use ssh.
If you have enabled two-factor authentication, or if you are accessing an organization that uses SAML single sign-on, you must provide a personal access token instead of entering your password for HTTPS Git.
Today I was trying to install sharp with yarn add sharp to work in my electron app, but I get the following error
Uncaught Error: The module ‘/Users/khoa/MyElectronApp/node_modules/sharp/build/Release/sharp.node’ was compiled against a different Node.js version using NODE_MODULE_VERSION 57. This version of Node.js requires NODE_MODULE_VERSION 54. Please try re-compiling or re-installing the module
Node.js Addons are dynamically-linked shared objects, written in C++, that can be loaded into Node.js using the require() function, and used just as if they were an ordinary Node.js module. They are used primarily to provide an interface between JavaScript running in Node.js and C/C++ libraries.
I’m using nvm to manage node version, and nvm list shows 8.4.0 as the latest node version I’m using.
1 2 3 4 5 6
v6.10.1 v7.8.0 v7.9.0 v8.0.0 -> v8.4.0 system
Searching on Node releases reveals that Node 8.4.0 has NODE_MODULE_VERSION of 57, so that is the node version npm uses to compile sharp
However, I can’t seem to find the NODE_MODULE_VERSION 54 that sharp is using. I tried node 8.0.0 which is believed to have NODE_MODULE_VERSION 54 but it didn’t work
After running electron-packager, the app does not accept copy, paste anymore. This is because the release build does not have menu with key binding to the clipboard by default. We can solve this by manually declaring the menu
I get a problem that electron always shows default app icon. I tried using png, NativeImage, different icon sizes but still the problem. When I use electron-packager to make release build, the icon shows correctly, so it must be because of Electron caching or somehow 😠
Go to node_modules -> electron -> dist, right click on Electron, choose View Info
Drag another icns into the icon on the top left
Release with electron-packager
icon must be specified with __dirname (we already did) for electron-packager to pick up correct icons
This property is used to set the APNS Token received by the application delegate. FIRMessaging uses method swizzling to ensure the APNS token is set automatically. However, if you have disabled swizzling by setting FirebaseAppDelegateProxyEnabled to NO in your app’s Info.plist, you should manually set the APNS token in your application delegate’s -application:didRegisterForRemoteNotificationsWithDeviceToken: method. If you would like to set the type of the APNS token, rather than relying on automatic detection, see: -setAPNSToken:type:.
By default, the FCM SDK generates a registration token for the client app instance on initial startup of your app. Similar to the APNs device token, this token allows you to target notification messages to this particular instance of the app.
Codable in Swift 4 changes the game. It deprecates lots of existing JSON libraries.
Generic model
API responses is usually in form of an object container with a key. Then it will be either nested array or object. We can deal with it by introducing a data holder. Take a look DataHolder
You can read everywhere that hamburger is mostly not recommended. Every navigation structure has their use cases, and for hamburger, I find these posts to have their points
In iOS there is a distinction between the coordinates you specify in your drawing code and the pixels of the underlying device
The purpose of using points (and the logical coordinate system) is to provide a consistent size of output that is device independent. For most purposes, the actual size of a point is irrelevant. The goal of points is to provide a relatively consistent scale that you can use in your code to specify the size and position of views and rendered content
On a standard-resolution screen, the scale factor is typically 1.0. On a high-resolution screen, the scale factor is typically 2.0
I like node.js because it has many cool packages. I wish the same goes for macOS. Fortunately, the below solutions provide a way to package node.js modules and use them inside macOS applications. It can be slow, but you save time by using existing node.js modules. Let’s give it a try.
pkg Package your Node.js project into an executable 🚀
nexe create a single executable out of your node.js apps
I’m trying to implement a tracker, so the idea is that it can inject subscription upon method calls. It is best suit for logging, analytics, and it leverages RxCocoa
Secondly, I tried with dateComponents. The component.year has changed, but it calendar still returns the original date, very strange !!. No matter what timezone and calendar I use, it still has this problem
1 2 3
var component = calendar.dateComponents(in: TimeZone.current, from: base) component.year = year Calendar.current.date(from: component)
Finally, I tried to be more explicit, and it works 🎉
1 2 3
var component = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: base) component.year = year Calendar.current.date(from: component)
We know that viewDidLoad is called when view is created the first time. So in the the Unit Test, if you use viewDidLoad to trigger, you will fall into a trap
1 2 3 4
functestSetup() { let controller = ListController() controller.viewDidLoad() }
Why is viewDidLoad called twice?
It is called once in your test
And in your viewDidLoad method, you access view, which is created the first time, hence it will trigger viewDidLoad again
The correct way
The best practice is not to trigger events yourself, but do something to make event happen. In Unit Test, we just access view to trigger viewDidLoad
1 2 3 4
functestSetup() { let controller = ListController() let_ = controller.view }
let name: String? = "venus" let planet = Planet(optionalValue: name)
One interesting fact about optional, is that it is a monad, so it has map and flatMap. Since enuminit(rawValue:) returns an optional, we need to use flatMap. It looks like this
1 2
let name: String? = "venus" let planet = name.flatMap({ Planet(rawValue: $0) })
Continue my post https://github.com/onmyway133/blog/issues/45. When you work with features, like map view, you mostly need permissions, and in UITests you need to test for system alerts.
Add interruption monitor
This is the code. Note that you need to call app.tap() to interact with the app again, in order for interruption monitor to work
1 2 3 4 5 6
addUIInterruptionMonitor(withDescription: "Location permission", handler: { alert in alert.buttons["Allow"].tap() returntrue })
app.tap()
Note that you don’t always need to handle the returned value of addUIInterruptionMonitor
Only tap when needed
One problem with this approach is that when there is no system alert (you already touched to allow before), then app.tap() will tap on your main screen. In my app which uses map view, it will tap on some pins, which will present another screen, which is not correct.
Since app.alerts does not work, my 2nd attempt is to check for app.windows.count. Unfortunately, it always shows 5 windows whether alert is showing or not. I know 1 is for main window, 1 is for status bar, the other 3 windows I have no idea.
The 3rd attempt is to check that underlying elements (behind alert) can’t be touched, which is to use isHittable. This property does not work, it always returns true
Check the content
This uses the assumption that we only tests for when user hits Allow button. So only if alert is answered with Allow, then we have permission to display our content. For my map view, I check that there are some pins on the map. See https://github.com/onmyway133/blog/issues/45 on how to mock location and identify the pins
1 2 3
if app.otherElements.matching(identifier: "myPin").count == 0 { app.tap() }
When there is no permission
So how can we test that user has denied your request? In my map view, if user does not allow location permission, I show a popup asking user to go to Settings and change it, otherwise, they can’t interact with the map.
I don’t know how to toggle location in Privacy in Settings, maybe XCUISiriService can help. But 1 thing we can do is to mock the application
Before you launch the app in UITests, add some arguments