Skip to content

fbernutz/dev-notes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

29 Commits
Β 
Β 
Β 
Β 

Repository files navigation

Felis dev notes ✌️

My personal list of some things I learned in ...

  • iOS, Swift
  • CocoaPods
  • git
  • Android, Kotlin

... and do not want to forget. Some things might be outdated.

iOS

#24: Deselect selected Row

How to deselect the selected row/item so it happens alongside the navigation back animation (including when using the interactive pop gesture):

extension UITableView {
    func deselectRow(along transitionCoordinator: UIViewControllerTransitionCoordinator?) {
        guard let selectedIndexPath = indexPathForSelectedRow else { return }

        guard let coordinator = transitionCoordinator else {
            deselectRow(at: selectedIndexPath, animated: false)
            return
        }

        coordinator.animate(alongsideTransition: { _ in
            self.deselectRow(at: selectedIndexPath, animated: true)
        }) { (context) in
            if context.isCancelled {
                self.selectRow(at: selectedIndexPath, animated: false, scrollPosition: .none)
            }
        }
    }
}

Found in twitter by @smileyborg.

#23: Using specific versions for CocoaPods

Have a look at: http://guides.cocoapods.org/using/the-podfile.html.

  • ~> 0.1.2 will get you a version up to 0.2 (but not including 0.2 and higher)
  • ~> 0.1 will get you a version up to 1.0 (but not including 1.0 and higher)
  • ~> 0 will get you a version of 0 and higher (same as if it was omitted)

#22: Clean StatusBar

See here.

Run:

// Xcode 11: Use Terminal to set the Status Bar of all booted Simulator devices to the most common variables.
xcrun simctl status_bar booted override --time "9:41" --batteryState charged --batteryLevel 100 --cellularMode active

#21: Find unused resources

See here.

Use Fengniao GitHub Repo to find unused resources in Xcode. Install it and exclude the folder you want to skip: fengniao --exclude Pods fastlane SnapshotTests DerivedData

#20: Speed up WWDC Videos

If you want to increase the video play rate of the sessions of WWDC, open Safari Dev Tools and type the following into the console. You'll see the video in 140% speed.

document.getElementsByTagName('video')[0].playbackRate = 1.4;

#19: Converting between String and Data

Idea from: https://twitter.com/johnsundell/status/1117812175593197568

Without optionals:

// String -> Data
let data = Data("String".utf8)

// Data -> String
let string = String(decoding: data, as: UTF8.self)

With optionals:

// String -> Data?
let data = "String".data(using: .utf8)

// Data -> String?
let optionalString = String(data: data, encoding: .utf8) 

#18: Localizing for plurals and variant widths using a stringsdict file

You can use a stringsdict file to add plural support for your strings or to add different strings for variant widths, e.g. a short expression, like "Hi" for small devices and a longer one, like "Welcome " for larger devices. Using the stringsdict file removes the extra logic for plural or variant widths support from your code to an easy to read xml file.

Plurals example

In practice, you define a key in your Localizable.stringsdict and define the values for your plurals. Xcode will automatically define the different keys for the specific language. For German there is zero, one and other but for Russian, there are more plural options. Xcode will automatically generate these keys, so you just have to add the correct values for the different strings.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>%d movie(s)</key>
        <dict>
            <key>NSStringLocalizedFormatKey</key>
            <string>%#@movies@</string>
            <key>movies</key>
            <dict>
                <key>NSStringFormatSpecTypeKey</key>
                <string>NSStringPluralRuleType</string>
                <key>NSStringFormatValueTypeKey</key>
                <string>d</string>
                <key>zero</key>
                <string>Keine Filme</string>
                <key>one</key>
                <string>Ein Film</string>
                <key>other</key>
                <string>%d Filme</string>
            </dict>
        </dict>
    </dict>
</plist>

In your project you can define your string like that:

static func movies(for counter: Int) -> String {
        return String.localizedStringWithFormat(NSLocalizedString("%d movie(s)", comment: "Movie(s)"), counter)
}

Important: Your key from NSLocalizedString has to be the same as in your Localizable.stringsdict.

Variant width example

To specify different strings for variant widths will sometimes solve some layout issues and this is a better solution than to always use a short string, because there would be much white space on larger devices.

Your Localizable.stringsdict can look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>welcome.width</key>
        <dict>
            <key>NSStringVariableWidthRuleType</key>
            <dict>
                <key>1</key>
                <string>Hi</string>
                <key>30</key>
                <string>Herzlich Willkommen</string>
            </dict>
        </dict>
    </dict>
</plist>

Like in the plurals example, it is important to use the same key in your swift declaration.

static let welcome = NSLocalizedString("welcome.width", comment: "Title for welcome")

There is one thing I don't understand with this. You set a value for the key and the compiler will automatically select the value for UIKit elements, but this did not work like I expected. With NSString.variantFittingPresentationWidth(_ width: Int) -> String you can set the width directly and the value will be returned for the appropriated width. If you call the method with 30 or bigger Herzlich Willkommen will be returned and if you call it with 1..<30 Hi will be returned. But you need to define when which string will be used. I wrote a helper as extension for String to decide which string will be returned depending on the screen device.

import UIKit

extension String {
    var forWidth: String {
        let width =
            Int(UIScreen.main.bounds.width) > 320
                ? 30
                : 1
        return (self as NSString).variantFittingPresentationWidth(width)
    }
}

In this case only for iPhone 5 or 5s the smaller string will be used. If you know any better solution, please let me know! πŸ˜‰

You call this helper every time the string is used, so the correct string will be returned from the Localizable.stringsdict.

static let welcome = NSLocalizedString("welcome.width", comment: "Title for welcome").forWidth

Info: The variantFittingPresentationWidth won't work for some UI elements, like UITabBarItem and UIPreviewAction. So in these cases the correct string will be returned when you print them, but they are not displayed. This felt like an unexpected behaviour for me, so I filed my first bug reports: rdar://41193537 and rdar://41193359

#17: Target Environment Simulator

Have a look at: https://swift.org/blog/swift-4-1-released

You can use #if targetEnvironment(simulator) to check if your app runs in a simulator. This is really useful, when you want to add some special handling for simulators, like taking a photo with a real device and using a dummy image in simulator.

import UIKit
class TestViewController: UIViewController, UIImagePickerControllerDelegate {
   // a method that does some sort of image processing
   func processPhoto(_ img: UIImage) {
       // process photo here
   }
   // a method that loads a photo either using the camera or using a sample
   func takePhoto() {
      #if targetEnvironment(simulator)
         // we're building for the simulator; use the sample photo
         if let img = UIImage(named: "sample") {
            processPhoto(img)
         } else {
            fatalError("Sample image failed to load")
         }
      #else
         // we're building for a real device; take an actual photo
         let picker = UIImagePickerController()
         picker.sourceType = .camera
         vc.allowsEditing = true
         picker.delegate = self
         present(picker, animated: true)
      #endif
   }
   // this is called if the photo was taken successfully
   func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
      // hide the camera
      picker.dismiss(animated: true)
      // attempt to retrieve the photo they took
      guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else {
         // that failed; bail out
         return
      }
      // we have an image, so we can process it
      processPhoto(image)
   }
}

#16: Delay requests while searching using GCD

Have a look at: https://www.swiftbysundell.com/posts/a-deep-dive-into-grand-central-dispatch-in-swift.

How to use GCD to delay sending requests while searching.

class SearchViewController: UIViewController, UISearchBarDelegate {
    // We keep track of the pending work item as a property
    private var pendingRequestWorkItem: DispatchWorkItem?

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        // Cancel the currently pending item
        pendingRequestWorkItem?.cancel()

        // Wrap our request in a work item
        let requestWorkItem = DispatchWorkItem { [weak self] in
            self?.resultsLoader.loadResults(forQuery: searchText)
        }

        // Save the new work item and execute it after 250 ms
        pendingRequestWorkItem = requestWorkItem
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250), execute: requestWorkItem)
    }
}

#15: WebViewController

iOS 8 and lower: Use WKWebView

iOS 9 or newer: Use SFSafariViewController

import SafariServices

guard let url = URL(string: "") else { return }
let safariController = SFSafariViewController(url: url)
present(safariController, animated: true, completion: nil)

Websites can be...

  • shared (+)
  • opened in safari (+)
  • displayed in reader mode directly (+)

Websites will be...

  • displayed in a different design. It will feel like something outside your app (-)

#14: Common Mistakes with Custom Fonts

Have a look at: https://codewithchris.com/common-mistakes-with-adding-custom-fonts-to-your-ios-app/

#13: IBOutletCollection

Have a look at: https://medium.com/@abhimuralidharan/what-is-an-iboutletcollection-in-ios-78cfbc4080a1.

IBOutletCollection can be used to configure an array of outlets.

@IBOutlet var buttons: [UIButton]! {
    didSet {
        for button in buttons {
            button.backgroundColor = .black
        }
    }
}

#12: debugPrint in release environment

Have a look at: http://swiftiostutorials.com/use-nslog-debugging/

debugPrint has nothing to do with debug and release environments. It just gives you some information more than the normal print.

To solve this, you can overwrite the print function with the following code and even add a releasePrint to print in release environment:

func releasePrint(_ object: Any) {
    Swift.print(object)
}

func print(_ object: Any) {
    #if DEBUG
    Swift.print(object)
    #endif
}

To use the DEBUG flag in code, you have to set the custom flag "DEBUG" in Build Settings > Active Compilation Conditions > Debug.

#11: Check for email addresses in String

Have a look at: http://developer.bombbomb.com/blog/2017/10/24/swift-data-detection/.

import UIKit

extension String {
    // Get email addresses in a string, discard any other content.
    func emailAddresses() -> [String] {
        var addresses = [String]()

        do {
            let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
            let matches = detector.matches(in: self, options: [], range: NSMakeRange(0, self.count))
            for match in matches {
                if let matchURL = match.url,
                    let matchURLComponents = URLComponents(url: matchURL, resolvingAgainstBaseURL: false), matchURLComponents.scheme == "mailto" {
                    let address = matchURLComponents.path
                    addresses.append(String(address))
                }
            }
        } catch {
            return []
        }

        return addresses
    }
}

let string = "ios@test.com is an email address"
string.emailAddresses() //results in "ios@test.com"

#10: StackView & Warnings

Have a look at: http://stackoverflow.com/a/37447714/6716961

Do not make layer changes in UIStackView!

#09: Find error in plist

plutil <PATH_TO_FILE>

#08: Right and LeftView in UITextField

You can use a rightView or/and leftView in UITextField.

When is it useful?

Well, it depends.

When you want to use a custom element as "side element" of the text field and want to define it in storyboard, you just do not have to use the rightView parameter. This is really useful when you have the view configured in code and then you can simply add it to the text field and you do not have to set constraints etc.

@IBOutlet var customView: UIView!

func textFieldDidBeginEditing(_ textField: UITextField) {
    if textField == password {
        password.rightView = customView
        password.rightViewMode = .always
    }
}

References

#07: Dispatch Group

  • Create a group
  • Enter a group
  • Won't finish until every group has left
  • Will notify on a specified queue when finished

Very useful with arrays of asynchronous requests and their completion handler.

let dispatchGroup = DispatchGroup()
var items = [Item]()
for item in items {
    if item.name == "name" {
        dispatchGroup.enter()
        doSomething(item.name) { _ in
            //do something
            dispatchGroup.leave()
        }
    }
}

dispatchGroup.notify(queue: DispatchQueue.main) {
    //do something
}

#06: ATS information

There are the following criteria, for which you have to add an exception:

  • http
  • lower TLS version than 1.2
  • cipher suites that do not support forward secrecy

Define the problem

  • Run in command line nscurl --ats-diagnostics <url> --verbose
  • Check url on the ssltest site from ssllabs.
  • You can test your browsers capabilities in ssllabs too.

Use exception

Available keys:

  • NSIncludesSubdomains
  • NSExceptionAllowsInsecureHTTPLoads
  • NSExceptionRequiresForwardSecrecy
  • NSExceptionMinimumTLSVersion
  • NSThirdPartyExceptionAllowsInsecureHTTPLoads
  • NSThirdPartyExceptionMinimumTLSVersion
  • NSThirdPartyExceptionRequiresForwardSecrecy
add in info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
    <dict>
        <key>url.com</key>
        <dict>
            <key>NSThirdPartyExceptionMinimumTLSVersion</key>
            <string>TLSv1.0</string>
            <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

References

Getting Ready for ATS Enforcement in 2017

#05: Attributed Text Helper

non-breaking

  • non breaking space: \u00a0
  • non breaking hyphen: \u2011

To use this in Localizable.strings, you have to use all caps (like \U00A0). Found here.

soft hyphen

Have a look at: http://stackoverflow.com/questions/33727659/how-to-perform-hyphenation-on-a-uilabel-sensitive-to-a-language-different-from-t

let title = "Title with Hyphen"

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.hyphenationFactor = 1.0

let paragraphAttributes = [NSAttributedStringKey.paragraphStyle: paragraphStyle]
let attributedStringWithHyphens = NSAttributedString(
    string: title,
    attributes: paragraphAttributes
)

let label = UILabel()
label.attributedText = attributedStringWithHyphens

#04: UITest helper

  • print(app.debugDescription)
  • Testing AutoLayout: Edit Scheme -> Options -> Double Length Pseudolanguage
  • use internal functions in test classes with @testable import ProjectName

#03: Configure UIKit elements on initializer

lazy var resultSearchController: UISearchController  = {
	let resultSearchController = UISearchController(searchResultsController: nil)
	resultSearchController.dimsBackgroundDuringPresentation = false
	resultSearchController.isActive = false
	resultSearchController.searchBar.sizeToFit()
	resultSearchController.searchResultsUpdater = self
	return resultSearchController
}()

#02: Simple animation with UIImage

@IBOutlet weak var loadingScene: UIImageView!

loadingScene.animationImages = [
    UIImage(named: "01")!,
    UIImage(named: "02")!,
    UIImage(named: "03")!,
    UIImage(named: "04")!,
    UIImage(named: "06")!
]

loadingScene.animationDuration = 1
loadingScene.startAnimating()

#01: UIImage with black & white effect

Have a look at: ο£Ώ Developer - CoreImageFilterReference.

func convertToBlackAndWhite() -> UIImage {
	let filter = CIFilter(name: "CIPhotoEffectMono")!
	filter.setDefaults()
	filter.setValue(CoreImage.CIImage(image: self)!, forKey: kCIInputImageKey)
	return UIImage(CGImage: CIContext(options:nil).createCGImage(filter.outputImage!, fromRect: filter.outputImage!.extent))
}

Xcode

#08: Support new iOS in old Xcode

Currently: Using iOS 14 devices with Xcode 11.5 (instead of Xcode 12)

See this gist by @steipete.

#07: Clear Cache for Launch Screen

The system caches resources from the launch screen. When changing the launch screen, this can be annoying.

import UIKit

public extension UIApplication {
    func clearLaunchScreenCache() {
        do {
            try FileManager.default.removeItem(atPath: NSHomeDirectory()+"/Library/SplashBoard")
        } catch {
            print("Failed to delete launch screen cache: \(error)")
        }
    }
}

Found here.

#06: Open Deep Link in Simulator

To test your apps deep links, you can open a deep link in the simulator like this:

xcrun simctl openurl booted "https://your-deep-link"

Found in twitter by @alex_v_bush.

#05: Select next occurrence

  1. select the thing you want to edit
  2. press-and-hold βŒ₯⌘E to "select next occurrence" until everything’s selected
  3. type in the replacement; all of β€˜em get changed

Found in twitter by @davedelong.

#04: Adding identifier to constraints

You can add identifier to constraints in code or in Storyboard. This makes it easier when you need to debug.

let constraint = topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
constraint.identifier = "ViewXYToTop"

Found in twitter by @twostraws.

#03: Weird problems with IBDesignable

When you have some weird problems with IBDesinable, try killall ibtoold and test again.

Another option is to read out the logs under ~/Library/Logs/DiagnosticReports and they are named IBDesignablesAgentCocoaTouch_*.crash.

#02: Set version number without fastlane

Have a look at: ο£Ώ Automating Version and Build Numbers Using agvtool

##set version number to specific version
agvtool new-marketing-version <your_specific_version>

##updating build number
agvtool next-version -all

##set build number to specific version
agvtool new-version -all <your_specific_version>

Otherwise, you can use fastlane for that too: https://docs.fastlane.tools/actions/increment_version_number/

#01: po in debugger

po in Xcode debugger means print object

test = 0
po [dictionary[@"test"] boolValue] //prints nil
print [dictionary[@"test"] boolValue] // prints NO

git

#05: Trunk based development and feature toggles

This was a new term for me, as I am mostly working with git flow at the moment. So I wrote together some points what trunk based development is and what I think when it might be a good choice.

git flow

These are some points I am thinking of when working with git flow.

πŸ‘ πŸ‘Ž
separate features large merge conflicts (when feature branch is used for a longer time)
independent from other developers frequently merging from main branch in feature branch
"standard" CI possible refactoring can be difficult
good visualized code reviews in gitlab with MR interaction of features is tested when features are already merged

Info: "standard" CI means build a new dev-version-app when merged on develop and prod-version with tags.

What is trunk based development?

= working on one main branch

  • activating features with feature toggles when they are finished
  • different features can be activated manually to test their interaction
  • always pull the latest changes before committing

trunk based

πŸ‘ πŸ‘Ž
nearly synchronous development different CI process
less merge conflicts many commits from different features (may look messy)
timely correct history code review process is not so smooth
easily testing interactions of features (during development)
faster releases (no merge before release)

Info: The "messy" commits can be solved with adding a ticket number in front of the commit message, e.g. [ABC-01] Update README. To build and deploy an application you could use manual git triggers with options to build and release a develop or a release version.

How can code reviews be integrated in process?

  • pushing changes on temporary branch (naming convention?)
  • do the code review (in gitlab with a MR β€” which will not be merged β€” or directly in Xcode)
  • fix discussions on temporary branch with additional commits
  • merge temporary branch on master with git merge --squash to have all the changes in one commit and set a meaningful commit message
  • delete the temporary branch (local and remote)

How can features be activated?

  • release version:
    • activated from outside with a backend (e.g. with firebase remote config?)
    • activated with a local configuration, like xml-file
  • develop version:
    • activated with "secret gestures", like five-finger-tap, etc.
    • activated with a local configuration, like xml-file
  • when features are controlled from outside the app
    • features can be released (or stopped) without an app update
    • easy A/B Tests possible

Feature toggles in Swift

This could look something like this:

enum Feature {
    case login
    case logout

    var ticketNumber: String {
        switch self {
        case .login:
            return "DS-01"
        case .logout:
            return "DS-02"
        }
    }

    var isActive: Bool{
        switch self {
        case .login:
            return true
        case .logout:
            return false
        }
    }
}

if Feature.login.isActive {
    print("new login")
} else {
    print("old login")
}

Additional Information & ToDos

  • CI-Process
    • actions like analyze and tests are running all the time
    • build and deploy actions: using manual git triggers to release new apps (with option to release develop or release version)
  • Is there a limit for the number of developers for using trunk based development?
  • Xcode 10 has a better integrated source control which will be helpful with trunk based development
  • Code review takes some time? (can lead to problems with every process)
  • Working with "Mono-Repositories": conflicts or inconsequences will be found even earlier
  • Duplicated code during refactoring?
  • Old features may be forgotten to be removed when new features are activated permanently
  • Architectural refactoring will be difficult

#04: Rebase

Do not force push! There is another solution most of the time.

Have a look at: github.com/k88hudson/git-flight-rules

When to rebase?

When you want to apply commits from another branch, like master, on top of your current local branch, like your feature branch.

How to rebase?

  1. Get the latest changes from master branch with git pull master.
  2. Checkout the branch you want to rebase with git checkout <branch>.
  3. Run git rebase master.
  4. (Solve possible conflicts and run git rebase --continue)
  5. Run git fetch to see changes in SourceTree

To cancel the rebase process early, run git rebase --abort.

You have pushed your feature branch and want to use rebase?

Do not use it, because you would have to use a force push and this will destroy the state of the repository for every other developer who has checked out the rebased branch before. Instead, you can use a simple merge to get the latest changes into your branch.

#03: Squash

Do not force push! There is another solution most of the time.

Have a look at: github.com/k88hudson/git-flight-rules

When to squash commits?

When you want to merge a branch with just one clean commit.

This is the case when working in feature branches. When you want to merge your feature, the exact steps you made are not relevant anymore most of the time (but they can be, of course). So while working on a feature you can commit wildly and you can try things out and revert them later. Basically, you can focus on the feature. When the feature is finished and about to be merged, you think about a meaningful commit message and with git merge <feature-branch> --squash you can commit all the changes in only one commit.

How to squash commits?

  1. You checkout the branch you want to integrate the new feature, with git checkout <branch>.
  2. Use git merge <feature-branch> --squash to squash all the commits from the feature branch. You now have all the changes unstaged.
  3. You can test your application once again and when everything is running, choose a meaningful commit message and commit all the changes on the branch.
  4. Now you can push the feature with git push and delete the feature branch on local and remote side.

There is a similar option for this directly integrated in GitHub as well. So you can merge features from a pull request with one single commit.

#02: Useful git commands

  • git reflog to see the history of the latest commands, every command has a number
  • git reset HEAD@{<number>} to get back to the state of when the command was executed.
  • git bisect to find the commit which introduced a bug.
git bisect start
git bisect bad # current version not working
git bisect good adf71de61 # worked in this commit
  • git checkout develop -- <path/to/file> to reset a file to the state on develop.

Two useful websites:

#01: Ignore already tracked XCUserstate

  1. Quit Xcode
  2. Navigate to the project directory and execute the following commands
ls -la #make sure you see the .git and .gitignore files
git rm --cached <projectFolder>.xcodeproj/project.xcworkspace/xcuserdata/<myUserName>.xcuserdatad/UserInterfaceState.xcuserstate
git commit -m "Removed file that shouldn't be tracked"
  1. Change ProjectFolder in the example above to the name of your project
  2. Relaunch Xcode and do a commit and verify that the xcuserstate file is no longer listed.

HTML

#01: <details> and <summary>

Use <details> and <summary> in GitHub to collapse large information in PR descriptions or comments.

<details>
    <summary>Screenshot</summary>

    ![name](link)

</details>

Found in twitter by @ashfurrow.

Android

#03: Logging

Have a look at the official documentation: https://developer.android.com/reference/android/util/Log

Import the Log class with import android.util.Log. There are different log leves, d is for Debug and e for error.

Two examples how to log something:

android.util.Log.d("feli", "feli: Logging is nice!")
android.util.Log.e("feli", "feli: " + e.getMessage(), e);

You see the logs printed in the Logcat section in Android Studio or print them out to your console with:

adb logcat -s feli

More information about how to use logcat: https://developer.android.com/studio/command-line/logcat

#02: Integrate ktlint into project

To enforce similar code style and conventions, it's a good idea to integrate a linter, like ktlint.

Add the following to app/build.gradle:

repositories {
    jcenter()
}

configurations {
    ktlint
}

dependencies {
    //...
    ktlint "com.pinterest:ktlint:0.32.0"
}

task ktlint(type: JavaExec, group: "verification") {
    description = "Check Kotlin code style."
    classpath = configurations.ktlint
    main = "com.pinterest.ktlint.Main"
    args "src/**/*.kt"
    // to generate report in checkstyle format prepend following args:
    // "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
    // see https://github.com/pinterest/ktlint#usage for more
}
check.dependsOn ktlint

task ktlintFormat(type: JavaExec, group: "formatting") {
    description = "Fix Kotlin code style deviations."
    classpath = configurations.ktlint
    main = "com.pinterest.ktlint.Main"
    args "-F", "src/**/*.kt"
}

To check code style, run:

./gradlew ktlint

To run formatter:

./gradle ktlintFormat

For further information, see the official installation guide for gradle.

#01: Swift is like Kotlin

To see the parallels, have a look at: http://nilhcem.com/swift-is-like-kotlin/

About

My personal list of things I once learned in iOS, Swift, CocoaPods and git (etc.) and do not want to forget.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published