Do You Have to Know How to Draw to Start a Clothing Line
Update note: Tom Elliott updated this tutorial for iOS 12, Xcode ten and Swift 4.2. Ray Wenderlich wrote the original.
This is the first in a series of Core Graphics tutorials that volition to take the mystery out of Cadre Graphics. You lot'll larn the APIs step by pace with practical exercises, starting by beautifying table views with Core Graphics.
Cadre Graphics is a really cool API on iOS. As a developer, you can utilize it to customize your UI with some really bully effects, often without fifty-fifty having to get an artist involved. Annihilation related to 2D drawing — like drawing shapes, filling them in and giving them gradients — is a skilful candidate for using Cadre Graphics.
With a history that dates dorsum to the very early days of Os X, Core Graphics is i of the oldest APIs still in use today. Perhaps this is why, for many iOS developers, Core Graphics can be somewhat intimidating at first: It'southward a large API and has plenty of snags to go caught on along the way. Even so, since Swift 3, the C-style APIs take been updated to expect and feel similar the modern Swift APIs you know and honey!
In this tutorial, you'll build a Star Wars Acme Trumps card app, which is comprised of a master view that contains a list of Starships:
…too every bit a particular view for each Starship.
In creating this app, yous'll learn how to get started with Core Graphics, how to fill and stroke rectangles and how to draw lines and gradients to make custom tabular array view cells and backgrounds.
You might want to buckle up; it'southward fourth dimension to have some fun with Core Graphics!
Getting Started
Use the Download Materials button at the superlative or lesser of this tutorial to download the starter and finished projects. Open the starter project and have a quick look around. The app is based on the Primary-Detail App template provided by Xcode. The master view controller contains a listing of Star Ships and the detail view controller shows details for each ship.
Open up MasterViewController.swift. At the superlative of the grade, notice a starships
variable, which contains an assortment of type Starship
and a dataProvider
variable of type StarshipDataProvider
.
Spring into StarshipDataProvider.swift past Control-clicking StarshipDataProvider
and selecting Jump to Definition. This is a simple course that reads a bundled file, Starships.json, and converts the contents into an array of Starship
.
You can discover the definition for a Starship
in Starship.swift. Information technology is only a simple struct
with properties for common properties of Starships.
Next, open up DetailViewController.swift. Divers at the pinnacle of the file before the class definition is an enum
, FieldsToDisplay
, which defines the human being readable titles for the Starship properties yous want to brandish as the cases in the enum
. In this file, tableView(_:cellForRowAt:)
is simply a big switch
statement for formatting the information for each Starship property into the right format.
Build and run the app.
The landing page is the MasterViewController
showing a list of Starships from the Star Wars universe. Tap to select the X-wing and the app will navigate to the detail view for that ship, which shows an image of an Ten-wing followed by various backdrop like how much it costs and how fast it tin wing.
This is a fully functional, if pretty ho-hum, app. Fourth dimension to add some bling!
Analyzing the Table View Style
In this tutorial, you'll add a different fashion to two different table views. Take a closer wait at what those changes look similar.
In the master view controller, each cell:
- Has a gradient from dark bluish to black.
- Is outlined in xanthous, drawn inset from the cell bounds.
And in the detail view controller:
- The table itself has a slope from dark blue to black.
- Each prison cell has a yellow splitter separating information technology from adjacent cells.
To describe both of these designs, you only need to know how to draw rectangles, gradients and lines with Core Graphics, which is exactly what you lot're virtually to learn. :]
Hello, Core Graphics!
While this tutorial covers using Cadre Graphics on iOS, it's of import to know that Cadre Graphics is available for all major Apple platforms including MacOS via AppKit, iOS and tvOS via UIKit and on the Apple tree Lookout via WatchKit.
You tin can think of using Core Graphics similar painting on a physical canvas; the ordering of cartoon operations matters. If you lot draw overlapping shapes, for instance, and so the final one you add together will be on superlative and overlap the ones below.
Apple architected Core Graphics in such a style that you, equally a programmer, provide instructions on the what to depict in a separate moment than the where.
Core Graphics Context, represented by the CGContext
class, defines the where. You tell the context what drawing operations to do. There are CGContexts
for drawing to fleck-mapped images, cartoon to PDF files and, nearly commonly, cartoon directly into a UIView
.
In this painting analogy, the Cadre Graphics Context represents the canvass where the painter paints.
Cadre Graphics Contexts are State Machines. That is, when yous set, say, a fill color you ready it for the entire sheet and whatever shapes y'all draw will have the aforementioned fill color until you change it.
Each UIView
has its own Core Graphics Context. To describe the contents of a UIView
using Core Graphics, yous depict(_:)
of the view. This is because iOS sets upwards the correct CGContext
for cartoon into a view immediately prior to calling draw(_:)
.
Now that you lot empathise the basics of how yous use Core Graphics inside UIKit, it's time for you to update your app!
Drawing Rectangles
To get started, create a new view file past selecting New ▸ File… from the File carte. Select Cocoa Touch on Grade, printing Side by side and then fix the class name to StarshipsListCellBackground. Go far a subclass of UIView, and then create the class file. Add the following code to your new form:
override func draw(_ rect: CGRect) { // one guard let context = UIGraphicsGetCurrentContext() else { return } // 2 context.setFillColor(UIColor.cerise.cgColor) // 3 context.fill up(bounds) }
Breaking this down line by line:
- Commencement, you get the current
CGContext
for thisUIView
instance usingUIGraphicsGetCurrentContext()
. Remember, iOS set this upwardly for you automatically prior to its callingdraw(_:)
. If you cannot get the context for whatever reason, you return early from the method. - Then, you gear up the fill color on the context itself.
- Finally, you tell it to fill the bounds of the view.
As you tin can meet, the Core Graphics API doesn't comprise a method for straight cartoon a shape filled with a color. Instead, a bit similar adding paint to a item castor, you gear up a color every bit a land of the CGContext
then, y'all tell the context what to pigment with that color separately.
You might have also noticed that when you called setFillColor(_:)
on the context you didn't provide a standard UIColor
. Instead, y'all must use a CGColor
, which is the fundamental data type used internally by Core Graphics to represent colors. It's super easy to convert a UIColor
to a CGColor
by just accessing the cgColor
property of any UIColor
.
Showing Your New Cell
To see your new view in activity, open MasterViewController.swift. In tableView(_:cellForRowAt:)
, add the post-obit code immediately subsequently dequeuing the cell in the starting time line of the method:
if !(cell.backgroundView is StarshipsListCellBackground) { prison cell.backgroundView = StarshipsListCellBackground() } if !(cell.selectedBackgroundView is StarshipsListCellBackground) { cell.selectedBackgroundView = StarshipsListCellBackground() }
This code sets the cells' background view to exist that of your new view. Build and run the app, and y'all will see a lovely, if garish, red groundwork in every jail cell.
Amazing! You can now describe with Core Graphics. And believe it or non, you've already learned a bunch of actually of import techniques: how to get a context to describe in, how to change the fill color and how to fill rectangles with a color. You lot can make some pretty nice UI with just that.
Only y'all're going to take it a step further and learn virtually one of the nearly useful techniques to make excellent UIs: gradients!
Creating New Colors
Yous're going to use the same colors again and again in this project, so create an extension for UIColor
to make these readily accessible. Go to File ▸ New ▸ File… and create a new Swift File chosen UIColorExtensions.swift. Replace the contents of the file with the following:
import UIKit extension UIColor { public static permit starwarsYellow = UIColor(red: 250/255, dark-green: 202/255, blue: 56/255, alpha: 1.0) public static allow starwarsSpaceBlue = UIColor(blood-red: 5/255, green: x/255, bluish: 85/255, alpha: 1.0) public static let starwarsStarshipGrey = UIColor(red: 159/255, light-green: 150/255, blue: 135/255, alpha: 1.0) }
This code defines iii new colors, which you lot tin access as static backdrop on UIColor
.
Drawing Gradients
Adjacent, since you're going to draw a lot of gradients in this project, add a helper method for drawing gradients. This volition simplify the projection past keeping the slope lawmaking in one place and avoid having to repeat yourself.
Select File ▸ New ▸ File… and create a new Swift File called CGContextExtensions.swift. Replace the contents of the file with the post-obit:
import UIKit extension CGContext { func drawLinearGradient( in rect: CGRect, startingWith startColor: CGColor, finishingWith endColor: CGColor ) { // 1 allow colorSpace = CGColorSpaceCreateDeviceRGB() // ii let locations = [0.0, 1.0] equally [CGFloat] // 3 let colors = [startColor, endColor] equally CFArray // four baby-sit let gradient = CGGradient( colorsSpace: colorSpace, colors: colors, locations: locations ) else { return } } }
There'southward a lot to this method:
- First, you ready up the right colour infinite. There'due south a lot you can do with color spaces, just you about always want to use a standard device-dependent RGB color infinite using
CGColorSpaceCreateDeviceRGB
. - Next, you prepare an array that tracks the location of each color within the range of the gradient. A value of 0 ways the start of the slope and 1 means the end of the gradient.
Note: Y'all can have three or more than colors in a gradient if y'all want and you can fix where each color begins in the gradient in an array like this one. This is useful for certain effects.
- Later that, you create an array with the colors that you passed into your method. Notice the use of
CFArray
, rather thanArray
, hither as yous are working with the lower level C APIs. - Then, yous create your gradient by initializing a
CGGradient
object, passing in the color infinite, assortment of colors and locations you previously made. If, for any reason, the optional initializer fails, you return early.
You lot at present accept a gradient reference, but it hasn't actually drawn anything yet — it'south just a pointer to the data you'll use when actually drawing later. It's nearly time to draw the gradient, but earlier yous do, information technology's time for a scrap more theory.
The Graphics State Stack
Remember that Core Graphics Contexts are state machines. Y'all have to be conscientious when setting state on a context, especially within functions that you pass a context or, as in this case, methods on the context itself, as you cannot know the state of the context before you alter it. Consider the post-obit code in a UIView
:
override func draw(_ rect: CGRect) { // ... go context context.setFillColor(UIColor.red.cgColor) drawBlueCircle(in: context) context.fill(someRect) } // ... many lines after func drawBlueCircle(in context: CGContext) { context.setFillColor(UIColor.blue.cgColor) context.addEllipse(in: bounds) context.drawPath(using: .make full) }
Glancing at this code, yous may recollect that information technology would depict a cerise rectangle and a blue circumvolve in the view, but you'd be wrong! Instead, this code draws a blue rectangle and a blue circle — simply why?
Because drawBlueCircle(in:)
sets a bluish fill color on the context and, because a context is a state auto, this overrides the cherry-red fill colour prepare previously.
This is where saveGState()
, and its partner method restoreGState()
), come in!
Each CGContext
maintains a stack of the graphics state containing almost, although not all, aspects of the current drawing environment. saveGState()
pushes a copy of the current land onto the graphics country stack, so y'all tin can use restoreGState()
to restore the context to that state at a later appointment and remove the state from the stack in the process.
In the example above, you lot should change drawBlueLines(in:)
like this:
func drawBlueCircle(in context: CGContext) { context.saveGState() context.setFillColor(UIColor.bluish.cgColor) context.addEllipse(in: bounds) context.drawPath(using: .fill) context.restoreGState() }
You lot can test this out for yourself past opening RedBluePlayground.playground in the Download Materials button at the acme or lesser of this tutorial.
Completing the Gradient
Armed with knowledge about the graphics state stack, it's fourth dimension to consummate cartoon the background gradient. Add the post-obit to the end of drawLinearGradient(in:startingWith:finishingWith:)
:
// 5 let startPoint = CGPoint(x: rect.midX, y: rect.minY) let endPoint = CGPoint(ten: rect.midX, y: rect.maxY) // half dozen saveGState() // 7 addRect(rect) clip() drawLinearGradient( gradient, showtime: startPoint, end: endPoint, options: CGGradientDrawingOptions() ) restoreGState()
- You get-go by computing the starting time and end points for the gradient. You ready this as a line from the top-middle to the bottom-middle of the rectangle. Helpfully,
CGRect
contains some case variables likemidX
andmaxY
to make this really simple. - Next, since you're about to modify the land of the context, you relieve its graphics land and finish the method by restoring information technology.
- Finally, you draw the gradient in the provided rectangle.
drawLinearGradient(_:offset:end:options:)
is the method that actually draws the slope merely, unless told otherwise, it will fill up the entire context, which is the entire view in your example, with the slope. Here, you lot only want to fill up the gradient in the supplied rectangle. To do this, you lot need to sympathize clipping.Clipping is an crawly characteristic in Core Graphics that lets you lot restrict cartoon to an capricious shape. All you have to do is add the shape to the context simply, so, instead of filling it similar you normally would, yous call
prune()
on the context, which then restricts all future drawing to that region.And then, in this case, yous'll set the provided rectangle on the context and prune before finally calling
drawLinearGradient(_:start:end:options:)
to draw the gradient.
Time to give this method a whirl! Open up StarshipsListCellBackground.swift and, later getting the current UIGraphicsContext
, supercede the code with the post-obit:
let backgroundRect = bounds context.drawLinearGradient( in: backgroundRect, startingWith: UIColor.starwarsSpaceBlue.cgColor, finishingWith: UIColor.black.cgColor )
Build and run the app.
You accept now successfully added a gradient groundwork to your custom prison cell. Well done, young Padawan! However, it would be fair to say the finished product isn't exactly looking dandy but now. Fourth dimension to fix that with some standard UIKit theming.
Fixing the Theme
Open Principal.storyboard and select the table view in the Principal scene. In the Attributes inspector, set Separator to None.
Then, select the Navigation Bar
in the Master Navigation Controller scene and fix the Navigation Bar Style to Blackness and deselect Translucent. Repeat for the Navigation Bar
in the Detail Navigation Controller scene.
Next, open MasterViewController.swift. At the end of viewDidLoad()
, add the following:
tableView.backgroundColor = .starwarsSpaceBlue
Then in tableView(_:cellForRowAt:)
, simply before returning the cell, set the color of the text:
prison cell.textLabel!.textColor = .starwarsStarshipGrey
Finally, open AppDelegate.swift and in awarding(_:didFinishLaunchingWithOptions:)
add the following but before returning:
// Theming UINavigationBar.appearance().tintColor = .starwarsYellow UINavigationBar.appearance().barTintColor = .starwarsSpaceBlue UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.starwarsStarshipGrey]
Build and run the app.
That's better! Your master table view is starting to look very space age. :]
Stroking Paths
Stroking in Core Graphics means cartoon a line along a path, rather than filling information technology, equally you lot did before.
When Core Graphics strokes a path, it draws the stroke line on the middle of the verbal edge of the path. This can cause a couple of common problems.
Outside the Bounds
First, if yous are cartoon around the border of a rectangle, a border, for example, Core Graphics won't depict half the stroke path by default.
Why? Because the context set for a UIView
only extends to the bounds of the view. Imagine stroking with a 1 bespeak edge around the border of a view. Considering Cadre Graphics strokes downwardly the center of the path, the line volition be one-half a betoken exterior the bounds of the view and half a indicate inside the bounds of the view.
A mutual solution is to inset the path for the stroke rect half the width of the line in each management then that it sits inside the view.
The diagram below shows a yellow rectangle with a reddish stroke one point wide on a gray background, which is striped at one signal intervals. In the left diagram, the stroke path follows the premises of the view and has been cropped. You can see this because the red line is half the width of the grey squares. On the correct diagram, the stroke path has been inset half a point and now has the correct line width.
Anti-Aliasing
2d, you need to be aware of anti-aliasing effects that can affect the advent of your border. Anti-aliasing, if you are unfamiliar with what it is (even if yous may have heard about it on a computer game settings screen!), is a technique rendering engines use to avoid "jagged" appearances of edges and lines when graphics beingness displayed don't map perfectly to physical pixels on a device.
Take the case of a one point edge around a view from the previous paragraph. If the border follows the bounds of the view, then Core Graphics will attempt to draw a line half a signal wide on either side of the rectangle.
On a non-retina display one point is equal to one pixel on the device. It is non possible to light upward just a one-half of a pixel, so Core Graphics volition use anti-aliasing to depict in both pixels, but in a lighter shade to give the appearance of only a single pixel.
In the following sets of screenshots, the left prototype is a non-retina display, the center epitome a retina display with a scale of two and the third epitome is a retina display with a scale of three.
For the commencement diagram, notice how the 2x paradigm doesn't show any anti-aliasing, as the half point either side of the yellow rectangle falls on a pixel boundary. All the same in the 1x and 3x images anti-aliasing occurs.
In this next prepare of screenshots, the stroke rect has been inset half a point, such that the stroke line aligns exactly with point, and thus pixel, boundaries. Find how there are no aliasing artifacts.
Adding a Border
Back to your app! The cells are starting to wait adept but you lot're going to add together some other affect to really make them stand out. This fourth dimension, you're going to draw a bright yellow frame around the edges of the cell.
You already know how to easily fill rectangles. Well, stroking around them is only as easy.
Open up StarshipsListCellBackground.swift and add the following to the lesser of depict(_:)
:
let strokeRect = backgroundRect.insetBy(dx: 4.5, dy: 4.5) context.setStrokeColor(UIColor.starwarsYellow.cgColor) context.setLineWidth(1) context.stroke(strokeRect)
Here, you create a rectangle for stroking that is inset from the background rectangle by 4.5 points in both the x and y directions. So yous gear up the stroke color to yellow, the line width to one point and, finally, stroke the rectangle. Build and run your project.
Now your starship listing really looks similar information technology comes from a milky way far, far abroad!
Building a Card Layout
While your master view controller is looking fancy, the item view controller is yet in need of some sprucing upwards!
For this view, you lot are going to showtime by drawing a gradient on the table view background by using a custom UITableView
subclass.
Create a new Swift File called StarshipTableView.swift. Replace the generated lawmaking with the post-obit:
import UIKit class StarshipTableView: UITableView { override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } allow backgroundRect = bounds context.drawLinearGradient( in: backgroundRect, startingWith: UIColor.starwarsSpaceBlue.cgColor, finishingWith: UIColor.blackness.cgColor ) } }
This should be starting to await familiar by at present. In the draw(_:)
method of your new table view subclass you get the current CGContext
then describe a slope in the bounds of the view, starting from blue at the top and heading into black at the bottom. Elementary!
Open up Main.storyboard and click on the TableView in the Particular scene. In the Identity inspector, set up the grade to your new StarshipTableView.
Build and run the app, so tap the Ten-wing row.
Your detail view now has a dainty full screen gradient running from top to lesser, just the cells in the table view obscure the best parts of the result. Fourth dimension to fix this and add a bit more flair to the detail cells.
Back in Primary.storyboard, select FieldCell in the Particular Scene. In the Attributes inspector, set the background to Articulate Color. Next, open DetailViewController.swift and, at the very lesser of tableView(_:cellForRowAt:)
, just earlier returning the cell, add the following:
prison cell.textLabel!.textColor = .starwarsStarshipGrey cell.detailTextLabel!.textColor = .starwarsYellow
This simply sets the cells' field name and value to more appropriate colors for your Stars Wars theme.
Then, later tableView(_:cellForRowAt:)
add the following method to style the table view header:
override func tableView( _ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int ) { view.tintColor = .starwarsYellow if let header = view as? UITableViewHeaderFooterView { header.textLabel?.textColor = .starwarsSpaceBlue } }
Hither, you lot're setting the tint color of the table views' header'due south view to the theme yellow, giving information technology a yellow background, and its text color to the theme blue.
Drawing Lines
As a final flake of bling, you'll add a splitter to each cell in the particular view. Create a new Swift file, this fourth dimension called YellowSplitterTableViewCell.swift. Replace the generated code with the post-obit:
import UIKit class YellowSplitterTableViewCell: UITableViewCell { override func describe(_ rect: CGRect) { guard permit context = UIGraphicsGetCurrentContext() else { return } let y = premises.maxY - 0.5 permit minX = bounds.minX permit maxX = premises.maxX context.setStrokeColor(UIColor.starwarsYellow.cgColor) context.setLineWidth(1.0) context.move(to: CGPoint(10: minX, y: y)) context.addLine(to: CGPoint(x: maxX, y: y)) context.strokePath() } }
In YellowSplitterTableVIewCell
, you are using Core Graphics to stroke a line at the bottom of the cells' bounds. Find how the y value used is half-a-point smaller than the bounds of the view to ensure the splitter is drawn fully inside the jail cell.
Now, you need to actually draw the line showing the splitter.
To depict a line between A and B, you first motion to point A, which won't cause Core Graphics to describe anything. You then add a line to point B which adds the line from point A to point B into the context. You tin so phone call strokePath()
to stroke the line.
Finally, open Main.storyboard over again and set the form of the FieldCell in the Detail scene to exist your newly created YellowSplitterTableViewCell using the Identity inspector. Build and run your app. So, open the X-wing particular view. Beautiful!
Where to Get From Here?
You tin can download the last project using theDownload Materials link at the top or lesser of this tutorial.
The download as well includes two playgrounds. RedBluePlayground.playground contains the example gear up out in the context saving/restoring department and ClippedBorderedView.playground demonstrates clipping a border unless it's inset.
Additionally, DemoProject is a total Xcode project which strokes a rect over a ane point filigree. This project is written in Objective-C so you can run it without modification on non-retina devices like the iPad 2, which require iOS 9, to see the anti-aliasing effects for yourself. But don't panic! It'due south easy to understand now you know the Core Graphics Swift API. :]
At this point, you should be familiar with some pretty cool and powerful techniques with Cadre Graphics: filling and stroking rectangles, drawing lines and gradients and clipping to paths. Not to mention your tabular array view now looks pretty cool. Congratulations!
If this tutorial was a little hard to follow, or you want to make certain to cover your basics, check out the First Core Graphics video series.
If you're looking for something more advanced, take a wait at the Intermediate Core Graphics course.
And if you lot don't feel like y'all can commit to a full grade yet, try the Core Graphics Commodity Series where you'll learn how to describe an entire app, including graphs, from scratch with Cadre Graphics!
Plus there are many more Core Graphics tutorials, all recently updated for Xcode 10, on the site.
If you have any questions or comments, please bring together the forum discussion below.
murrellkrourstage.blogspot.com
Source: https://www.raywenderlich.com/475829-core-graphics-tutorial-lines-rectangles-and-gradients
0 Response to "Do You Have to Know How to Draw to Start a Clothing Line"
Post a Comment