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 CIFilter
s. 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.
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.
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 UIImageView
s 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.
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 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:
The app title has width 320 px and height 44 px and attaches to the top of the screen.
The image views each have a width and height of 320 px.
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:
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:
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 UIImageView
s, 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 UIView
—containerView—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:
class ViewController: UIViewController {
We need to create an array of CIFilter
names:
var CIFilterNames = [ "CIPhotoEffectChrome", "CIPhotoEffectFade", "CIPhotoEffectInstant", "CIPhotoEffectNoir", "CIPhotoEffectProcess", "CIPhotoEffectTonal", "CIPhotoEffectTransfer", "CISepiaTone" ]
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()
:
override func prefersStatusBarHidden() -> Bool { return true }
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()
:
override func viewDidLoad() { super.viewDidLoad() var xCoord: CGFloat = 5 let yCoord: CGFloat = 5 let buttonWidth:CGFloat = 70 let buttonHeight: CGFloat = 70 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:
var itemCount = 0 for i in 0..<CIFilterNames.count { itemCount = i // Button properties let filterButton = UIButton(type: .Custom) filterButton.frame = CGRectMake(xCoord, yCoord, buttonWidth, buttonHeight) filterButton.tag = itemCount filterButton.addTarget(self, action: #selector(ViewController.filterButtonTapped(_:)), forControlEvents: .TouchUpInside) filterButton.layer.cornerRadius = 6 filterButton.clipsToBounds = true // 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:
// Create filters for each button let ciContext = CIContext(options: nil) let coreImage = CIImage(image: originalImage.image!) let filter = CIFilter(name: "\(CIFilterNames[i])" ) filter!.setDefaults() filter!.setValue(coreImage, forKey: kCIInputImageKey) let filteredImageData = filter!.valueForKey(kCIOutputImageKey) as! CIImage let filteredImageRef = ciContext.createCGImage(filteredImageData, fromRect: filteredImageData.extent) 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.
// Assign filtered image to the button filterButton.setBackgroundImage(imageForButton, forState: .Normal)
Adding Buttons to the ScrollView
Just a few more lines to complete our viewDidLoad()
method:
// Add Buttons in the Scroll View xCoord += buttonWidth + gapBetweenButtons filtersScrollView.addSubview(filterButton) } // END FOR LOOP // Resize Scroll View filtersScrollView.contentSize = CGSizeMake(buttonWidth * CGFloat(itemCount+2), yCoord) } // 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.
func filterButtonTapped(sender: UIButton) { let button = sender as UIButton imageToFilter.image = button.backgroundImageForState(UIControlState.Normal) }
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.
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.
You've created an IBAction
this time, and here's the code that must be placed into it:
// Save the image into camera roll UIImageWriteToSavedPhotosAlbum(imageToFilter.image!, nil, nil, nil) let alert = UIAlertView(title: "Filters", message: "Your image has been saved to Photo Library", delegate: nil, cancelButtonTitle: "OK") 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:
Thanks for reading, and I'll see you next time! Please check out some of our other tutorials on Swift and iOS app development.
Comments