Many apps use Core Data for persistence and also need to import data from a server. Imported items typically have a unique identifier which can be used for identifying them. The count of imported items can be high therefore it is preferred to batch insert the items instead of adding them one by one. Core Data framework has a specialized request for this called NSBatchInsertRequest which is available since iOS 13. If we combine batch insert with Core Data constraints then we can achieve a flow where new items are only created when the store does not have an item for the unique identifier. All the other items already available in the persistent store are updated (instead of deleting the old item and reinserting it). In this blog post let’s take a look on how it works with a sample app which displays a list of Product entities with a name and a unique serial code attributes.
Product entity with “name” and “serialCode” attributes.
Constraints on the entity can be set in the model editor. For making sure that only one Product with a serial code of X exists in the persistent store then we will need to add a constraint on the serialCode attribute. Core Data framework will then make sure that only one entity with unique serial code exists in the persistent store. Neat, no need to query the store first for existing products and manually checking for possible duplicates.
CoreData constraint set to Product entity.
With a constraint set up, let’s take a look on the batch insert. Apple added NSBatchInsertRequest to Core Data framework in iOS 13. As we added a constraint then we need to tell Core Data what to do if there is already an item for the unique serial code. If we set NSManagedObjectContext‘s merge policy to NSMergeByPropertyObjectTrumpMergePolicy before executing the batch insert then Core Data goes and updates existing items with incoming attribute values fetched from a server. If there is not an item in the store with serial code then a new item is inserted. In summary, we get a behaviour where existing items are updated and missing items are inserted when importing items from a server. The flow of fetching data from a server, running batch insert on a background context and then refreshing fetched results controller can be seen below.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Import function in a view model which fetches a list of products and inserts into a persistent store.
Summary
NSBatchInsertRequest is a welcoming change which makes it easy to insert and update existing items already in the persistent store. Setting up a constraint on a unique identifier and setting merge policy on a context enables us to handle SQL upserts without much code.
I have been writing a new blog post at least every second week and therefore in 2020 I have published total of 27 blog posts. It is time to take a look back on the 2020 and see which blog posts were the most popular ones. I am just looking at the total count of views per blog post.
UILabel supports displaying attributed strings with link attributes but what it does not support is allowing to tap on hyperlinks and opening those in Safari. An Alternative way is using an UITextView which does support opening hyperlinks but on the other hand it is a more heavy view component and therefore might not be the best choice when we just need to display some text with hyperlinks. This time lets create a simple UILabel subclass which adds support for opening hyperlinks and custom hyperlink styles.
Creating NSAttributedStrings with hyperlinks
Before we jump into implementing an UILabel subclass HyperlinkLabel, let’s first take a look on how to create a NSAttributedString with hyperlinks. In the example app we will have several labels with different configurations: default and custom link styles and left, centre, right text alignments. UITextView has a linkTextAttributes property but in our UILabel subclass we’ll need to implement custom link styling ourselves. The approach we are going to take is creating a new NSAttributedString.Key value named hyperlink and adding text attributes when the attribute is present.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Let’s now create a convenience method which creates NSAttributedString and sets it to the HyperlinkLabel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The final NSAttributedString is created by first initializing a mutable version with a format string. The format string follows the familiar format specifiers like we used with NSString and String APIs when dealing with re-orderable arguments. Format specifiers are replaced with new instances of NSAttributedStrings where the string value equals to a hyperlink name and the URL value is stored on the NSAttributedString.Key.link or NSAttributedString.Key.hyperlink attribute. The former gives us the default link style defined by Apple and the latter our custom link style.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Creating HyperlinkLabel which supports tapping on hyperlinks
UILabel does not provide access to its text layout and therefore it is not possible to know which hyperlink was tapped. For finding the tapped hyperlink we’ll need to use our own NSLayoutManager, NSTextStorage, and NSTextContainer. If we configure those properly we can figure out which character was tapped and therefore if it was part of the hyperlink. If it is, then we can let UIApplication to open the tapped URL. Let’s contain this in a function which gives us a configured NSTextStorage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Now we can use this function when handling UITouch events. We’ll add another private function which will take current UITouches and figure out which hyperlink was tapped. The general flow consists of creating a NSTextStorage with the function we just defined, then asking from the NSLayoutManager which character index was tapped. NSLayoutManager returns the closest character index of the touch. Therefore, we’ll need to go one step deeper and ask for the actual bounding rectangle of the glyphs representing the character and then verifying if the touch location was inside the glyph’s bounding rectangle. This is important when dealing with different text alignments and when tapping on the free space around characters. After figuring out which character was tapped we’ll need to check for the hyperlink attributes. If the tapped character has either attribute set, then we can conclude that we tapped on a hyperlink. This function can then be called in touchesEnded and if we tapped on a hyperlink, then we can open it. One thing to note is that userInteractionEnabled needs to be set to true before UILabel can handle touch events.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Next thing we need to do is overriding attributedText property and handling the custom style of our custom hyperlink attribute. If the attributed text has this attribute set, then we will apply custom hyperlink text attributes. The same way as Apple’s link attribute works, when the attributed string gets displayed then custom styling is used. Secondly, we’ll set the UILabel’s font to the attributed string’s ranges which do not have a font attribute set. UILabel internally use UILabel’s font when font attributes are not set, so we want to force the same behaviour when the stored attributed string is set to our own NSTextStorage. If we do not do this, then NSAttributedString just uses its default font and the displayed string is not going to be equal to the string set to NSTextStorage. This in turn will lead to invalid character index calculations because fonts are different.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We created a UILabel subclass which has a capability of opening tapped links. Useful, if we need just a label for displaying some text along with hyperlinks. Please take a look on the full implementation available here: HyperlinkLabel.
Just a couple of weeks ago I wrote about resizing images on iOS: Resizing UIImages with aspect fill on iOS. As I am currently building a macOS, iOS, watchOS app I realized that I need a multiplatform image resizer. As this app deals with full size photos then I need to resize those photos for avoiding excessive memory usage. Apple’s CoreGraphics framework provides APIs what are compatible with all the before mentioned platforms. Therefore, let’s revisit the image scaler created in Resizing UIImages with aspect fill on iOS and let’s refactor it to use purely multiplatform supported CGImages.
We’ll skip the part which discusses calculating a rectangle in the original image which is then resized for assuring the image is resized equally on both axes. It is important because the aspect ratio of the original image most of the time does not equal to the aspect ratio of the target size. Let’s now take a look at cropping and resizing the original image. Cropping is easy to do because CGImage already has a convenient cropping(to:) method. On the other hand, resizing requires setting up a CGContext with the same parameters as the cropped image but the pixel size must be set to the target size. When we now draw the cropped image to the context, then CoreGraphics takes care of resizing the image.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
I have created a multiplatform sample app which just has a single SwiftUI view shared with iOS and macOS (it is not a Mac Catalyst app). Because we use CGImages then all the code can be shared. The view just loads a full size photo, scales it, and displays it. In the example view we have resized the image by taking account the current displayScale. Depending on the displayScale value we need to make the pixel size of the CGImage larger by the factor of the displayScale. For example, if the point size is 200×200 points, displayScale is 3.0, then the pixel size of the CGImage needs to be 600×600. This will give us a nice and crisp end result when the image is rendered.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
CoreGraphics is available on multiple Apple platforms and it provides tools for cropping and resizing images. With just a little bit of refactoring we have turned UIImage based image scaler into CoreGraphics backed scaler which can be used on multiple platforms.
Lots of apps need to deal with selecting or taking photos but in SwiftUI we’ll need to wrap UIKit’s UIImagePickerController with a SwiftUI view.
Example application presenting a UI for opening image picker.
Wrapping UIImagePickerController in SwiftUI
UIImagePickerController has been available since iOS 2 and it supports both selecting photos from photo albums and taking new photos with a camera. If we would like to use an image picker in a SwiftUI view then the first step is wrapping this view controller with a SwiftUI view. UIViewControllerRepresentable protocol defines required methods for representing an UIViewController. We’ll provide a completion handler for passing back the selected image. We need to implement a coordinator which acts as a delegate for the UIImagePickerController. When the imagePickerController(_:didFinishPickingMediaWithInfo:) delegate method is called, then we can call the completion handler and handle the selected image in a SwiftUI view. As UIImagePickerController supports both the camera function and accessing existing photos, we’ll add a source type property for configuring which mode to use.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ImagePicker view which wraps UIImagePickerController.
The ImagePicker can then be presented with the fullScreenCover view modifier. The presented state and the selected image is stored in the view’s view model. When the image picker is displayed and an image is selected, the completion handler is called and the selectedImage property is updated in the view model which in turn reloads the SwiftUI view.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A SwiftUI view containing an image preview and buttons for taking or choosing a photo.
Summary
Wrapping UIKit views with a SwiftUI view is fairly simple. The coordinator object is a perfect fit for handling delegate methods which UIKit views often provide. As we saw, adding a SwiftUI compatible image picker was pretty easy to do. Please check the full example project on GitHub.
In model-view-view model (MVVM) architecture the view model observes the model and provides data for the view by transforming it when needed. When the user interacts with the view and changes the data in it then the view model’s responsibility is to propagate those mutations back to the model object. Therefore, the important part in MVVM is how to manage data flows between objects. This time we’ll take a look on observing key-value observing (KVO) compatible model objects with Combine framework in SwiftUI view. The example view what we’ll build looks like this:
SwiftUI view which enables editing package related information and displays a summary of the package.
Model layer
The model object represents a package which contains information about the recipient, the sender, and the contents. The recipient and the sender are represented by a Person object which includes a first name, a last name, and a postal address. The contents is an array of immutable PackageContent objects. In Swift, we can use KVO by specifying @objc and dynamic modifiers on properties. Dynamic means that method dispatch is using objective-c runtime and therefore all the types must be representable in objective-c runtime. This immediately adds restrictions to the types we can use. When writing pure Swift code I do not recommend using KVO but sometimes we just need to use it. One example is NSManagedObject from the CoreData framework. But in this app we are not dealing with NSManagedObject but with a simple NSObject subclass instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The view object is responsible for describing the UI and rendering data represented by the view model. We have a simple form for modifying the recipient’s first name, last name, and the street name (for keeping this UI simple I left out all the other postal address related properties). At the bottom of the view we have a text object which just describes the package and a button for adding new items to the package’s contents. Whenever any of the package’s properties change, the view needs to reload. View reload is done through the @StateObject property wrapper (read mode about observing view models in MVVM in SwiftUI and @StateObject and MVVM in SwiftUI).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The view model’s responsibility is to observe the model object and propagating view changes to the model. It acts as a transformation layer where we can transform any data in the model to anything suitable for displaying. In the example below we are reading CNPostalAddress and only returning street name and reading multiple properties and returning a summary string. View models make it easy to contain such logic and also make it more easy to test.
Foundation framework defines a publisher named NSObject.KeyValueObservingPublisher which can be used for observing KVO compatible properties. One of the approaches is to use this publisher and then bind the model changes to the view model’s own property. Combine framework provides a convenient assign operator which takes a target publisher as an argument. Convenient because we can connect it with @Published properties in the view model. @Published properties automatically notify the ObservableObject’s objectWillChange publisher which is observed by a SwiftUI view. As soon as the property changes, SwiftUI view picks up the change and reloads itself. Note that we’ll also need to propagate changes back to the model when user updates the view and therfore the @Published property. This can be achieved by connecting property’s publisher with dropFirst, removeDuplicates and assign publishers where the latter assigns the value to the model object. Drop first is used for ignoring the initial value of the property. One downside is that now we can have the same information both in the view model and in the model. But on the other hand it makes the data streams easy to read and no need to have extra observation for triggering the view reload by manually calling send() on the objectWillChange publisher.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Another approach what we can use is providing properties in the view model which return a Binding. This allows us to write the transformation code inside the get and set closures. This is what we have done with the street property. Note that we’ll still need to observe the model as the model can change at any point. Binding just provides a way of accessing the value. Therefore, we’ll need to set up an observation and calling send() on the objectWillChange publisher.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If we go back to the SwiftUI view and connect all the properties then the full implementation of the view model looks like this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Key-value observing is getting less and less used after the introduction of Combine and SwiftUI. But there are still times when we need to connect good old KVO compatible NSObject subclasses with a SwiftUI view. Therefore, it is good to know how to handle KVO in SwiftUI views as well.
Resizing images is an important topic when we need to display images which do not match with the intended display size. For example, rendering much large images in a small rectangle. UIImageView supports scaling images automatically but that becomes inefficient when dealing with larger images. In this blog post we’ll take a look on how to crop and resize images to fill a target size while keeping the original aspect ratio.
Cropping and scaling UIImages
The end goal of this exercise is to create a small ImageScaler struct which supports cropping the original image and resizing it to the target size. The final instance of the UIImage has smaller size which means that less memory is required for rendering the image.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
As the first step let’s take a look on how to write the cropping and scaling logic. NSHipster has a great post about the different techniques what we can use for resizing images. We are going to use UIGraphicsImageRenderer for creating the scaled image. One important thing to note is that when we use CGContext for drawing the image then we need to flip the coordinate system because UIImage’s and CGContext’s coordinates do not match (UIImage uses upper left corner, CGContext bottom left corner). Coordinates can be transformed from the UIImage coordinate system to the CGContext coordinate system by combining translation and scale transforms. First we’ll move the image and then flip it in the opposite direction so that the final frame stays in the image rect.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Next step after applying the affine transform is to crop the image. UIImage has a cgImage property but it can be nil when the instance was initialized with a CIImage backing storage. Therefore, we’ll need to handle both cases. Apple has a convenience drawing method for CIImage which already knows how to handle cropping. On the otherhand CGImage needs to be cropped first and then drawn. The full implementation of the crop and resize becomes:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Now we have cropping and resize logic available. Depending on the target size of the image we’ll need to figure out which parts of the original image should be cropped so that the original aspect ratio does not change. Note that the original image size and the target size can have different aspect ratios: square, portrait, landscape. Therefore, we’ll need to handle all the cases. Let’s start by adding convenience properties to CGSize.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
After that we can add a method on CGRect which calculates a CGRect of the original image, what can be drawn in the target image. There are 9 different combinations what we need to handle. But the core logic stays the same: scale the current rectangle so that it fills the target size while keeping the original aspect ratio. Then chop off the sides which go over the target size and center the image in the target size.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Scaling rect to the target size with keeping the initial aspect ratio.
Finalizing the ImageScaler
We can proceed with creating a single static method which takes care of scaling the original image to the target size while keeping the aspect ratio. The whole implementation looks like this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Jazzy is an excellent tool for generating API documentation for Swift and Objective-C projects. Let’s take a look how to generate and publish API documentation in GitHub with Jazzy.
Installing Jazzy
Installation: sudo gem install jazzy
Jazzy config file for a Swift package
Configuration options can be passed into Jazzy directly with the command or adding them to the a file, by default it is .jazzy.yaml. We’ll use the default path as then we can run Jazzy without any command line arguments: the configuration is read from the configuration file. For seeing all the available configuration options run jazzy config -h. Just note that the configuration file expects snakecase (build-tool-arguments becomes build_tool_arguments). Let’s take a look on Swift package IndexedDataStore which can be built both for macOS and iOS. It has some additional functions for iOS and therefore it is preferred to build iOS target when running Jazzy. Otherwise API documentation would not contain those public functions meant for iOS. Typically Swift package is built using swift build command. The current state is that there is no easy way for just specifying the target OS to the swift build command. What we can do instead is using xcodebuild command which knows how to build Swift packages as well. We’ll just need to specify the scheme, sdk, and destination arguments. If we now run jazzy command without any arguments, it will read the configuration file, and generate API documentation which includes functions which require UIKit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Configuration file for Jazzy which builds documentation for iOS.
GitHub action for publish API documentation
Thankfully there is a GitHub action available for publishing API documentation with Jazzy. We can set up a GitHub action with a name PublishDocumentation and store it in the repository’s .github/workflows folder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
GitHub action which publishes API documentation to GitHub pages.
The GitHub action is triggered either manually or when publishing a release. Manual trigger is called workflow_dispatch and when it is set, GitHub webpage will display a “Run workflow” button. This is very handy when testing GitHub actions. Another thing to note is that publish-jazzy-docs requires repository access because it needs to write the documentation files to the gh-pages branch. For giving repository access we’ll need to set up personal access token with repo scope. Secondly, we’ll need to paste the created token to the repository’s secrets. In this example, we have added a secret named ACCESS_TOKEN and the value is the personal access token. Now, if we have committed and pushed the GitHub action then we can open the repository on GitHub.com, navigate to actions, selecting PublishDocumentation, and using Run workspace button for triggering the wokrflow. If everything goes well, then the workspace creates a gh-pages branch which in turn creates a new GitHub page. In this case the URL to the new GitHub page looks like: https://laevandus.github.io/IndexedDataStore/ (link). This is what we wanted to achieve, API documentation publiched on GitHub.
Summary
We set up Jazzy for a Swift package and used it to generate API documentation. Generated API documentation was published to a GitHub page.
Shape protocol in SwiftUI is used for defining views which render custom shapes. Shapes have one required method which takes in a rect and returns a Path. In addition to view protocol, shape conforms to Animatable protocol as well. Therefore we can quite easily make our custom shape to animate from one state to another. We’ll use two parameters for defining our Wave shape: amplitude and frequency. Amplitude dictates the height of the wave and frequency the distance between wave peaks.
Animating wave shape.
SwiftUI view displaying an animatable wave shape
Let’s take a look on an example view which displays custom Wave shape. We’ll use @State property wrappers for storing amplitude and frequency because we want to change those values when running the app. Those properties are updated with random values when pressing a button. The wave has blue fill color, fixed height, and basic easeInOut animation. The animation is used when amplitude and/or frequency change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Content view rendering a wave shape with a button starting an animation.
Animatable wave shape
Like mentioned in the introduction, the Shape protocol defines a required method which has a rect argument and returns a Path. The path starts from the top left edge. Sine function is used for calculating y coordinates for every x coordinate with a 1 point step. Right, bottom and left edges are just straight lines.
Animatable protocol defines an animatableData property and because we have two parameters (amplitude and frequency) we’ll need to use AnimatablePair type. If there would be more parameters then AnimatablePair should contain one or more AnimatablePair types (and so on). Note that values in animatableData must conform to VectorArithmetic protocol which Double type already does.
When animation is running then SwiftUI calculates amplitude and frequency values based on the current animation frame and sets it to the animatableData property. Then new Path value is calculated and rendered.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We took a look at the Shape protocol and created a wave shape. In addition, we made the wave to animate from one amplitude and frequency state to a new state.
SwiftLint is a tool for validating the style and conventions in Swift files. It is easy to integrate to a project. Minimum set up consists of installing SwiftLint and adding a build phase to your app target in Xcode. SwiftLint comes with a default list of rules, but if you would like to change the rules then it is required to create a configuration file in yaml. Let’s take a look on how to install, add build step, and use a separate configuration file with a custom set of rules.
SwiftLint is available in homebrew so the easies way is to install it with brew install swiftlint command. Next step is to add the build phase to a target. I am gonna use one of my example app which is available on GitHub: SwiftPackageAppWorkspace. This project uses a workspace and includes an app project and Swift package.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Click on the ButtonGallery project in the file navigator, then on the iOS target and build phases. Will use the + button for adding a new run script phase. Note that we already use the config argument for letting SwiftLint know where the config file exists (by default SwiftLint looks for .swiftlint.yml file in the same folder the project file is).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Build phase calling swiftlint with custom configuration file in one folder up from the .xcodeproj.
Build phase which triggers swiftlint with custom configuration.
Last step is to add a custom configuration file to the repository checkout. We’ll add it to the checkout’s root folder which is the parent folder of the ButtonGallery.xcodeproj. I have went through the full list of rules available for SwiftLint and picked the ones which match with my coding style. SwiftLint has a list of default rules. The list of evaluated rules can be expanded with opt_in_rules and rules from default list can be disabled with disabled_rules list. Also I prefer to have else on a newline so I added statement_position configuration with statement_mode: uncuddled_else. Included defines a list of folder paths relative to the .xcodeproj calling swiftlint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The next time the target is built it will run SwiftLint with custom configuration and show warnings and/or errors in Xcode.
Warnings generated by SwiftLint in the example project.
Summary
SwiftLint is easy to set up and helps to keep the code style consistent in projects. In additional to style SwiftLint is capable of suggesting different coding conventions like use enum instead of struct with only static functions. For making it easy to add custom configuration to new project I have set up a command alias in ~/.zshrc which looks like this: alias xcode_lint_add='cp ~/Dev/swiftlint.yml swiftlint.yml && mate swiftlint.yml' Run xcode_lint_add in the root of the cloned project.