1. Code
  2. Mobile Development
  3. iOS Development

Apply Photo Filters With Core Image in Swift

Scroll to top
Final product imageFinal product imageFinal product image
What You'll Be Creating

The Power of the Core Image Framework

Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images in iOS and OS X. Apple has made a few great pre-made photo effects that you can easily use for your photography apps, with names such as Instant, Process, Sepia, Tonal, etc.

Core Image Reference

The iOS developer library provides a good explanation of Core Image image processing in the Core Image Reference Collection.

I suggest that you also check out the Core Image Filter Reference page in order to get a complete list of available CIFilters. Please note that not all of these are compatible with iOS; some of them only work on OS X. 

We will use the following Core Image filters:

  • CIPhotoEffectChrome
  • CISepiaTone
  • CIPhotoEffectTransfer
  • CIPhotoEffectTonal
  • CIPhotoEffectProcess
  • CIPhotoEffectNoir
  • CIPhotoEffectInstant
  • CIPhotoEffectFade

Note: This tutorial is written using Xcode 7.3 and Swift 2.2, with a Deployment Target set to 8.0, so your app will work on older devices too.

Let's Get Started!

Create a Project and Add Views

Open Xcode and create a new project, iOS Single View Application. Choose Swift as the language and Universal for devices. You'll get a blank UIViewController in the Storyboard and a couple of .swift files: ViewController.swift and AppDelegate.swift.

Select the controller in the Storyboard and set its size as iPhone 3.5-inch in the right-hand panel, under Simulated Metrics. This will help you to adapt views for the iPhone 4S, the Apple device with the smallest screen size available.

Set controller size to iPhone 4SSet controller size to iPhone 4SSet controller size to iPhone 4S

We won't use Auto Layout in this tutorial, because it messes up layouts on bigger devices like iPad and iPad Pro. Disable it by selecting File Inspector and uncheck Use Auto Layout. Then click the Disable Size Classes button from the popup.

Disable Auto Layout and Size ClassesDisable Auto Layout and Size ClassesDisable Auto Layout and Size Classes

Now find a JPEG image—either from the web or from your Mac—and drag it below the Assets.xcassets folder in the Project navigator panel. We'll use this as a sample image that we can apply our filters to. Name this file picture.jpg; we will call it later in the code.

You will have to drag some additional views into the controller. Select the Object library on the bottom-right corner of Xcode and drag a UIView to the center of the screen. Resize the view to 320 x 320 px and center it horizontally. 

Now add two UIImageViews to your Storyboard, by finding them in the Object library and dragging them into the main UIView. Resize these image views to fill that main view (we'll look at how to set their autoresizing  properties later). Assign picture.jpg to the first image view with the Attributes inspector panel.

Assign a jpg image to the first UIImageView Assign a jpg image to the first UIImageView Assign a jpg image to the first UIImageView

Next, drag a UIScrollView to the bottom of the screen, and set its width to fit the controller's width. You may also add a UILabel to the top of the controller and set its text to Filters. This will be the title of your app. 

Lastly, add a UIButton to the top-left corner of the screen and make its title Save.

Set Autoresizing and Layout Views

The Size inspector panel can be shown by clicking on the little ruler icon in the top-right corner of the screen. Do that now and start editing the view sizes by selecting the UIButton. The Size inspector will show you its x and y coordinates on the screen (keep in mind that x is 0 at the left side of the screen and y is 0 at the top). Set the width and height to 44 px.

Set Save button size and marginSet Save button size and marginSet Save button size and margin

Set the button's autoresizing mask to attach to the top and left sides of the screen.

Now select all the other views, one by one, and adjust their size and position as follows:

Set title Label size and margins Set title Label size and margins Set title Label size and margins

The app title has width 320 px and height 44 px and attaches to the top of the screen.

Set imageViews size and marginsSet imageViews size and marginsSet imageViews size and margins

The image views each have a width and height of 320 px.

Set ScrollView size and margins Set ScrollView size and margins Set ScrollView size and margins

Finally, the scroll view (for filter options) has a width of 320 px and a height of 80 px.

Declaring Views in the .swift File

One of the nicest features of Xcode is the possibility to split the workspace into two parts and have the Storyboard on one side and a Swift file on the other side. In order for you to do that, you must click on the Assistant Editor icon on the top-right corner of your Xcode window:

Assistant Editor iconAssistant Editor iconAssistant Editor icon

If your ViewController is selected in Storyboard, the right section will automatically show its relative .swift file. In case that doesn't happen, you can try clicking on the horizontal menu on the top of the swift file and switch Manual to Automatic:

Find the controllers swift file in the right side of the Xcode window Find the controllers swift file in the right side of the Xcode window Find the controllers swift file in the right side of the Xcode window

Now let's attach some of our views to the ViewController.swift file. From the Document Outline panel, select the UIView that contains the two UIImageViews, hold down Control (or the right mouse button), and drag your mouse pointer underneath to get the class declaration of your view controller.

Release the mouse button and a popup will appear. Type in the name of the UIViewcontainerView—and click the Connect button.

You've just added a declaration for an IBOutlet of type UIView to your .swift file. Do the same thing for the other image views: drag the blue line below each instance you'll declare, and name the first one originalImage and the second one imageToFilter. Make sure that originalImage is the one with picture.jpg as an image. 

Then connect the UIScrollView and name it filtersScrollView. This will store all the buttons for applying filters to your picture.  

We'll declare our UIButton later as an IBAction. This button will allow us to save our filtered image into the Photo Library of our device.

Let's Code!

Creating an Array of Core Image Filters

In Swift, you can declare global variables by simply placing them outside a class declaration, in our case this one:

1
class ViewController: UIViewController {

We need to create an array of CIFilter names:

1
var CIFilterNames = [
2
    "CIPhotoEffectChrome",
3
    "CIPhotoEffectFade",
4
    "CIPhotoEffectInstant",
5
    "CIPhotoEffectNoir",
6
    "CIPhotoEffectProcess",
7
    "CIPhotoEffectTonal",
8
    "CIPhotoEffectTransfer",
9
    "CISepiaTone"
10
]

As mentioned at the beginning of this tutorial, we have to use the original Core Image filter names in order for our app to recognize them. We'll assign these filters to the buttons we'll create later, which will apply the filter to our image.

Hiding the Status Bar

Most of the time, you may want to hide the Status Bar from your screen. Since Xcode 7.0, it is no longer possible to set the hidden property of the Status Bar in Info.plist, so you must add this method right above viewDidLoad()

1
override func prefersStatusBarHidden() -> Bool {
2
    return true
3
}

Creating the Filter Buttons

The viewDidLoad() method is a default instance that Xcode creates each time you add a .swift file to your project; it gets called when the screen and all its views get loaded. If you wanted to perform some action even before that happens, you could use the viewDidAppear() or viewWillAppear() methods, but we don't need to. So let's add a few variables of type CGFloat right below super.viewDidLoad():

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    var xCoord: CGFloat = 5
5
    let yCoord: CGFloat = 5
6
    let buttonWidth:CGFloat = 70
7
    let buttonHeight: CGFloat = 70
8
    let gapBetweenButtons: CGFloat = 5

Those values are needed to place a row of buttons inside our filtersScrollView. As you can see in the above code, the xCoord is the X position where a button will be placed, yCoord is the Y position, buttonWidth and buttonHeight are its size, and gapBetweenButtons is the space between each button. 

Now we need to actually create the buttons by using a for loop that will duplicate a custom UIButton and place it into the filtersScrollView based on the above values. 

Place this code right below those CGFloat instances:

1
    var itemCount = 0
2
        
3
    for i in 0..<CIFilterNames.count {
4
        itemCount = i
5
            
6
        // Button properties
7
        let filterButton = UIButton(type: .Custom)
8
        filterButton.frame = CGRectMake(xCoord, yCoord, buttonWidth, buttonHeight)
9
        filterButton.tag = itemCount
10
        filterButton.addTarget(self, action: #selector(ViewController.filterButtonTapped(_:)), forControlEvents: .TouchUpInside)
11
        filterButton.layer.cornerRadius = 6
12
        filterButton.clipsToBounds = true
13
        
14
        // CODE FOR FILTERS WILL BE ADDED HERE...

Let's see what happens in this code. itemCount is a variable we'll use later to add our filter buttons as subviews of the filtersScrollView. You may notice that we've declared this variable with the var prefix. That's because it will be modified by the for loop. If you want to declare constants in Swift, you use the let prefix, as we did for the filterButton.

Using the new for loop syntax of Swift 2.2, we don't need to write i++ anymore. The loop will increment i automatically by counting from 0 to the number of elements of the CIFilterNames array.

Inside that for loop, we create a custom button, set its CGRect values, assign it a tag, and add a target action to it that calls a function we'll see later: filterButtonTapped().

In order to make our button look nice, with round corners, we use the layer property and set its corner radius to 6. Then we clip the image to be contained in its bounds, otherwise it would cover the rounded corners.

Add the Image to the Buttons and Apply Filters

The next piece of code must be added below the comment of the previous one:

1
        // Create filters for each button

2
        let ciContext = CIContext(options: nil)
3
        let coreImage = CIImage(image: originalImage.image!)
4
        let filter = CIFilter(name: "\(CIFilterNames[i])" )
5
        filter!.setDefaults()
6
        filter!.setValue(coreImage, forKey: kCIInputImageKey)
7
        let filteredImageData = filter!.valueForKey(kCIOutputImageKey) as! CIImage
8
        let filteredImageRef = ciContext.createCGImage(filteredImageData, fromRect: filteredImageData.extent)
9
        let imageForButton = UIImage(CGImage: filteredImageRef);

Here we initialize a CIContext and CIImage to let Core Image work on originalImage (picture.jpg) that each button will show. Then we init a filter variable of type CIFilter that will be called by each button through the for loop based on the CIFilterNames array.

Our filter instance needs to set its default state, and then it becomes the input key for images. Then we create the data object from it and its image reference, which we'll use to create a UIImage right away that will be attached to the button. 

Since the filters we've selected for this tutorial are pre-made by Apple, we don't need to apply any additional value (such as intensity, color, etc.). If you want to get information about other CIFilters, you may check the Core Image Filter Reference page.

In the last line of this section, we finally set the button's background image that we've previously created.

1
        // Assign filtered image to the button
2
        filterButton.setBackgroundImage(imageForButton, forState: .Normal)        

Adding Buttons to the ScrollView

Just a few more lines to complete our viewDidLoad() method:

1
        // Add Buttons in the Scroll View
2
        xCoord +=  buttonWidth + gapBetweenButtons
3
        filtersScrollView.addSubview(filterButton)
4
    } // END FOR LOOP 
5
        
6
        
7
    // Resize Scroll View
8
    filtersScrollView.contentSize = CGSizeMake(buttonWidth * CGFloat(itemCount+2), yCoord)
9
10
} // END viewDidload()

We're adding buttons as sub views to the filtersScrollView based on their position and the width and space they should keep between each other. Then finally we close the for loop.

Lastly, we have to set the contentSize of our ScrollView to fit all the buttons. Here's where we finally use the itemCount variable previously declared, converted to CGFloat (because CGSizeMake it doesn't accept Int values).

The Filter Button Action

We're almost done, with just a few more lines of code!

In the ViewController class, outside viewDidLoad(), create the filterButtonTapper() function. This will be called every time you tap on one of the buttons we've generated before.

1
func filterButtonTapped(sender: UIButton) {
2
    let button = sender as UIButton
3
        
4
    imageToFilter.image = button.backgroundImageForState(UIControlState.Normal)
5
}

We need to create an instance of UIButton first, and then set the imageToFilter's image based on that button's background image, which has already been filtered by the code placed into viewDidLoad()

Make sure that the UIImageView called imageToFilter overlays the originalImage, as shown below, otherwise the app will not show you the processed image because the original image will hide it.

imageToFilter must be in front of originalImageimageToFilter must be in front of originalImageimageToFilter must be in front of originalImage

Saving the Processed Picture

We've got to the end of this tutorial, and there's just one more function to add in your .swift file. That's the Save button we've previously placed in the Storyboard. Hold Control and the mouse button, drag a blue line from the UIButton to an empty space within your class, release the mouse button, and a new popup will show up. Here you have to change the Connection type to Action and enter the name of your method—in this case savePicButton—and click the Connect button.

Declare IBAction for the Save buttonDeclare IBAction for the Save buttonDeclare IBAction for the Save button

You've created an IBAction this time, and here's the code that must be placed into it:

1
    // Save the image into camera roll

2
    UIImageWriteToSavedPhotosAlbum(imageToFilter.image!, nil, nil, nil)
3
        
4
    let alert = UIAlertView(title: "Filters",
5
                message: "Your image has been saved to Photo Library",
6
                delegate: nil,
7
                cancelButtonTitle: "OK")
8
    alert.show()

The first line simply saves the image contained into imageToFilter directly in the Photo Library of your device or iOS Simulator. Then we fire a simple UIAlertView that confirms that the operation has been made.

OK, let's run our app and see what happens if we tap the buttons on the bottom. If you've done everything correctly, your app should look like this:

App showing image with color filter appliedApp showing image with color filter appliedApp showing image with color filter applied

App showing image with color filter appliedApp showing image with color filter appliedApp showing image with color filter applied

App showing message that image has been saved to Photo LibraryApp showing message that image has been saved to Photo LibraryApp showing message that image has been saved to Photo Library

Thanks for reading, and I'll see you next time! Please check out some of our other tutorials on Swift and iOS app development.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.