قالب وردپرس درنا توس
Home / IOS Development / Core Graphics Tutorial: Lines, rectangles and gradients

Core Graphics Tutorial: Lines, rectangles and gradients



Update Note : Tom Elliott updated this tutorial for iOS 12, Xcode 10 and Swift 4.2. Ray Wenderlich wrote the original.

This is the first in a series of Core Graphics tutorials that will take the mystery out of Core Graphics. You learn the APIs step by step with practical exercises, and start by beautifying table views with Core Graphics.

Core Graphics is a very cool API on iOS. As a developer, you can use it to customize your user interface with some very nice effects, often without having to get an artist involved. Everything related to 2D drawing – as drawing forms, filling them in and giving them gradients – is a good candidate for using Core Graphics.

With a history dating back to the very first days of OS X, Core Graphics is one of the oldest APIs still in use today. Perhaps this is the reason why for many iOS developers, Core Graphics may be somewhat daunting at first: It's a great API and has many snags getting caught along the way. But since Swift 3, the C-style APIs have been updated to look like the modern Swift APIs you know and love!

In this tutorial, you build a Star Wars Top Trumps Shortcut, which consists of

 Finished Star Ship List

… as well as a detail view for each star ship.

<img class = "aligncenter size-medium wp-image-195207 bordered" src = "https://koenig-media.raywenderlich.com/uploads/2019/01/starship_list_finished.png" alt = "19659002] When You create this app, learn how to get started with Core Graphics, how to fill and stretch rectangles, and how to draw lines and gradients to create custom table views and backgrounds.

You may want to buckle up; to have fun with Core Graphics!

Getting Started

Use the button Download materials at the top or bottom of this tutorial to download the startup image and finished projects Open the startup project and take a quick look The app is based on the Master-Detail App template provided by Xcode. The main view controller contains a list of Star Ships and the detail view controller displays details for each ship.

Open MasterViewController.swift On top one of the class marks a star ship variable, which contains a number of types Starship and a dataProvider variable of the type StarshipDataProvider StarshipDataProvider

Enter StarshipDataProvider.swift by Command Click StarshipDataProvider and select Jump to Definition . This is a simple class that reads a combined file, Starships.json and converts the content into a number of Starship .

You find the definition for a Starship in Starship.swift . There is only one simple structure with properties of common characteristics of Starships.

Next, open DetailViewController.swift . Defined at the top of the file before the Class Department is a enum FieldsToDisplay that defines the human readable titles for the Starship properties you want to display as the cases in enum . In this file, [ tableView (_: cellForRowAt :) is just a large swap statement for formatting the data for each Starship property in the correct format.

Build and run the app.

The landing page is MasterViewController which shows a list of Starships from the Star Wars universe. Press to select X-wing and the app will navigate to the detail view of that ship, showing an image of an X-wing followed by various features such as how much it costs and how quickly it can fly.

 Starting starship details

This is a fully functional, if quite boring, app. Time to add some bling!

Analyzing the Table View Style

In this tutorial, you add a different style to two different table views. Take a closer look at what these changes look like.

In the main view, each cell has:

  • a gradient from dark blue to black.
  • Shown in yellow, drawn input from the cell lines.

  Gradient gradient gradient detail

And in the detail display control:

  • The table itself has a gradient from dark blue to black.
  • Each cell has a yellow splitter that separates it from adjacent cells.

  Starship detail gradient detail

To draw both of these designs, you just need to know how to draw rectangles, gradients and lines with Core Graphics, which is exactly what you should learn. :]

Hello, Core Graphics!

While this tutorial covers using Core Graphics on iOS, it is important to know that Core Graphics is available to all major Apple platforms, including MacOS via AppKit, iOS and tvOS via UIKit and on Apple Watch via WatchKit.

You can think of using Core Graphics as painting on a physical canvas; ordering drawing operations cases. If you draw overlapping characters, for example, the last one you add will be at the top and overlap them below.

Apple Architecture Core Graphics in such a way that as a developer you instruct on what to draw in a different moment than where .

Core Graphics Context represented by the class CGContext defines where . You tell the context what drawing operations to do. There is CGContexts to draw by-pass images, draw PDF files and, most often, draw directly into a UIView .

In this paint analog, Core Graphics Context represents the canvas where the painter paints.

Core Graphic Contexts are State Machines . That is, when you put, say a fill color, put it on the whole canvas, and any shapes you draw will have the same fill color until you change it.

Each UIView has its own Core Graphics context. To draw the contents of a UIView using Core Graphics, you must write the drawing code within draw (_ :) of the view. This is because iOS creates the correct CGContext to draw a view immediately before calling draw (_ :) .

Now that you understand the basics of using Core Graphics in UIKit, it's time to update your app!

Drawing rectangle

To get started, create a new viewer file by selecting New ▸ File ... from File ] Menu. Select Cocoa Touch Class press Next and then set the class name to StarshipsListCellBackground . Make it a subclass of UIView and then create the class file. Add the following code to your new class:

  override func draw (_ rect: CGRect) {
// 1
vakt la kontekst = UIGraphicsGetCurrentContext () otherwise {
return
}
// 2
context.setFilColor (UIColor.red.cgColor)
// 3
context.fill (borders)
}

  1. First, you get the current CGContext for this UIView example using UIGraphicsGetCurrentContext () . Remember that iOS will set this up for you automatically before calling draw (_ :) . If you cannot get the connection for any reason, you will return early from the method.
  2. Next, set the fill color for the context itself.
  3. Finally, tell it to fill the boundaries of the display. [19659050] As you can see, the Core Graphics API does not contain a method of direct drawing a shape filled with a color. Instead, just like adding paint to a particular brush, set a color as a state of CGContext and then tell the context what to paint with that color separately.

    You may also have noticed that when you called setFillColor (_ :) on the context, you did not give a standard UIColor . Instead, use a CGColor which is the basic data type used internally by Core Graphics to represent colors. It is very easy to convert a UIColor to a CGColor by accessing only the cgColor property of anyone UIColor .

    Showing your new Cell

    To see your new view in action, open MasterViewController.swift . In tableView (_: cellForRowAt :) add the following code immediately after dequeuing the cell in the first line of the method:

      if! (Cell.backgroundView is StarshipsListCellBackground) {
    cell.backgroundView = StarshipsListCellBackground ()
    }
    
    if! (cell.selectedBackgroundView is StarshipsListCellBackground) {
    cell.selectedBackgroundView = StarshipsListCellBackground ()
    }
    

    This code indicates the cell's background view as your new view. Build and run the app and you'll see a lovely, if garish, red background in each cell.

     Red cells

    Amazing! You can now draw with Core Graphics. And believe it or not, you've already learned a bunch of really important techniques: how to get a context to pull in, how to change the fill color and how to fill rectangles with a color. You can do some pretty nice user interfaces with just that.

    But you should take it a step further and learn about one of the most useful techniques for creating good user interfaces: gradients!

    Creating New Colors

    To use these same colors over and over again in this project, create an extension for UIColor to make them easily accessible. Go to File ▸ New ▸ File ... and create a new Swift File called UIColorExtensions.swift . Replace the contents of the file with the following:

      import UIKit
    
    extension UIColor {
    public static la starwarsYellow =
    UIColor (red: 250/255, green: 202/255, blue: 56/255, alpha: 1.0)
    public static la starwarsSpaceBlue =
    UIColor (red: 5/255, green: 10/255, blue: 85/255, alpha: 1.0)
    public static la starwarsStarshipGrey =
    UIColor (red: 159/255, green: 150/255, blue: 135/255, alpha: 1.0)
    }
    

    This code defines three new colors that you can access as static properties on UIColor .

    Drawing Gradients

    Next page to draw many gradients in this project, add a help method to draw gradients.

    Select File ▸ New ▸ File ... and create a new Swift File called CGContextExtensions.swift . Replace the contents of the file with the following:

      import UIKit
    
    CGContext extension {
    func drawLinearGradient (
    in line: CGRect,
    startingWith startColor: CGColor,
    finished with endColor: CGColor
    ) {
    // 1
    la colorSpace = CGColorSpaceCreateDeviceRGB ()
    
    // 2
    leave places = [0.0, 1.0] as [CGFloat]
    
    // 3
    let colors = [startColor, endColor] as CFArray
    
    // 4
    guard la gradient = CGGradient (
    colorsSpace: colorSpace,
    colors: colors,
    places: places
    ) otherwise {
    return
    }
    }
    }
    

    There is much on this method:

    1. First, set up properly color space . There is a lot you can do with color sites, but you will almost always use a standard device-dependent RGB color space using CGColorSpaceCreateDeviceRGB .
    2. Then, set up an array that tracks the position of each color within the range of the gradient. A value of 0 means the start of the gradient and 1 means the end of the gradient.

      Note : You can have three or more colors in a gradient if you want, and you can specify where each color begins in the gradient of a table like this. This is useful for certain effects.

    3. Then make a matrix of the colors you passed into the method. Notice the use of CFArray instead of Array here while working with the lower level APIs.
    4. Then, create the gradient by initializing a CGGradient object, passing in the color space, variety of colors, and places you have previously created. If for some reason you do not select the optional initializer, you will return early.

    You now have a gradient reference, but it hasn't drawn anything yet - there's only one point to the information you want to use when you actually draw later. It's almost time to draw the gradient, but before doing so, it's a bit more theory.

    Graphics State Tablet

    Remember that Core Graphics Contexts are state machines. You need to be careful when inserting a state into a context, especially within functions that you transfer a context or, as in this case, methods in the context itself, since you cannot understand the state of the context before changing it. Consider the following code in a UIView :

      override func draw (_ rect: CGRect) {
    // ... get context
    
    context.setFilColor (UIColor.red.cgColor)
    drawBlueCircle (i: context)
    context.fill (someRect)
    }
    
    // ... many lines later
    
    func drawBlueCircle (in context: CGContext) {
    context.setFilColor (UIColor.blue.cgColor)
    context.addEllipse (i: boundaries)
    context.drawPath (using: .fill)
    }
    

    Glancing at this code, you might think it would draw a red rectangle and a blue circle in the view but you would have been wrong! Instead, this code draws a blue rectangle and a blue circle - but why?

     Leaking blue fill

    Because drawBlueCircle (i :) sets a blue fill color in context, and because a context is a state machine, this overrides the red-filled color set earlier.

    This is where saveGState () and its partner method restoreGState () ) came in!

    Each CGContext maintains a stack of graphics status that contains most but not all aspects of today's drawing environment. saveGState () pushes a copy of the current state on the graphics status stack, and you can then use restoreGState () to restore the context of the current state at a later time and remove

    I the example above you should change drawBlueLines (i :) like:

      func drawBlueCircle (in context: CGContext) {
    context.saveGState ()
    context.setFilColor (UIColor.blue.cgColor)
    context.addEllipse (i: boundaries)
    context.drawPath (using: .fill)
    context.restoreGState ()
    }
    

     Using SaveGState to Stop the Blue Leak

    You can test this yourself by opening RedBluePlayground.playground in Download Materials button on the top or the bottom of this tutorial.

    Complete the gradient

    Armed with knowledge of the graphics state stack, it is time to complete the drawing of the background degree. Add the following to the end of drawLinearGradient (in: startingWith: finishingWith :) :

      // 5
    la startPoint = CGPoint (x: rect.midX, y: rect.minY)
    la endPoint = CGPoint (x: rect.midX, y: rect.maxY)
    
    // 6
    saveGState ()
    
    // 7
    addRect (rect)
    clips ()
    drawLinearGradient (
    gradient;
    start: startPoint,
    end: endPoint,
    options: CGGradientDrawingOptions ()
    )
    
    restoreGState ()
    
    1. You start by calculating the start and end points of the gradient. You specify this as a line from the top center to the bottom center of the rectangle. Helpful, CGRect contains some instance variables like midX and maxY to make this very easy.
    2. Next, since you are about to change the state of the Context, you save the graphics status and exit the method by restoring it.
    3. Finally, drag the gradient into the specified rectangle. drawLinearGradient (_: start: end: options :) is the method that actually draws the gradient, but unless otherwise stated, it will fill the entire context, which is the whole view in your case, with the gradient. Here you will only fill the gradient in the supplied rectangle. To do this, you need to understand clipping .

      Clipping is a great feature of Core Graphics that lets you restrict drawing to any shape. All you have to do is add the shape of the context, but instead of filling it as you usually do, call clip () on the context, which limits all future drawing to that region.

      So, in this case you will put the specified rectangle on the context and clip before finally dialing drawLinearGradient (_: start: end: options :) to draw the gradient.

    Time to give this method a whirl! Open StarshipsListCellBackground.swift and after receiving the current UIGraphicsContext replace the code with the following:

      la backgroundRect = bounds
    context.drawLinearGradient (
    i: backgroundRect,
    startingWith: UIColor.starwarsSpaceBlue.cgColor,
    finishing with: UIColor.black.cgColor
    )
    

    Build and run the app.

     Invalid cell gradient

    You have now added a gradient background to your custom cell. Well done, young Padawan! However, it would be fair to say that the finished product does not look good right now. Time to fix it with any standard UIKit theme.

    Attaching the theme

    Open Main.storyboard and select the table view in the main image .

    . Common Table View "/>

    Then select Navigation Line in in Master Navigator Controller and set the Navigation Line Style to Black and clear Transparent . Repeat for Navigation Bar in Detail Navigation Controller .

     Black Navigation Bar

    Next, open MasterViewController.swift ]. At the end of viewDidLoad () add the following:

      tableView.backgroundColor = .starwarsSpaceBlue
    

    Then put in the color of the text:

      cell.textLabel! .TextColor = .starwarsStarshipGrey
    

    Finally open AppDelegate.swift and in application (_: didFinishLaunchingWithOptions :) add the following just before returning:

      // Theming
    UINavigationBar.appearance (). TintColor = .starwarsYellow
    UINavigationBar.appearance (). BarTintColor = .starwarsSpaceBlue
    UINavigationBar.appearance (). TitleTextAttributes =
    [.foregroundColor: UIColor.starwarsStarshipGrey]
    

    Build and run the app.

     Less ugly cell gradient

    It's better! Your main table view begins to look very big. :]

    Stroking Paths

    Stroking in Core Graphics means drawing a line along a path, instead of filling it as you did before.

    When Core Graphics beats a path, it draws the stretch line on the center of the exact edge of the path. This can lead to a few common problems.

    Outside the boundaries

    First, if you pull around the edge of a rectangle, a boundary, for example, will not pull the half stretch path as default.

    Why? Because the context created for a UIView only extends to the boundaries of the display. Imagine stretching with a one-point boundary around the edge of a view. Because Core Graphics beats in the middle of the path, the line will be half a point beyond the boundaries of the view and a half point inside the boundaries of the view.

    One common solution is to introduce the path of the battle half Half the width of the line in each direction so that it is inside the view.

    The diagram below shows a yellow rectangle with a red stroke a point wide on a gray background, which is striped with one point interval. In the left diagram, the trajectory follows the boundaries of the display and is cut. You can see this because the red line is half the width of the gray squares. On the right diagram, the trajectory is inserted half a point and now has the correct line width.

     Boundary and input 1/2 points

    Anti-Aliasing

    Secondly, be aware of anti-aliasing effects that may affect the appearance of the border. Anti-aliasing, if you are not familiar with what it is (even though you may have heard about it on a computer game setting screen!), Is a technology rendering engine used to avoid "shaded" appearances of edges and lines when graphics are not displayed map perfectly for physical pixels on a device.

    Take the example of a one-point boundary around a view from the previous paragraph. If the boundary follows the boundaries of the display, Core Graphics will attempt to draw a line half a point wide on either side of the rectangle.

    On a non-retinal display, one point is equal to one image on the device. It is not possible to illuminate only one half of a pixel, so Core Graphics will use anti-aliasing to draw in both pixels, but in a lighter shade to give the appearance of only a single pixel. 19659002] In the following set of screens, the left image is a non-retinal display, the middle image is a retinal display with a scale of two and the third image is a retinal display with a scale of three.

    For the first chart, note how the 2x image does not show any anti-aliasing, as the half point on either side of the yellow rectangle falls on a pixel boundary. But in 1x and 3x images, anti-aliasing occurs.

     Stroke with different screen scales

    In this next set of screens, the stretching rectifier has been inserted half a point, so that the line line accurately adjusts with point, and therefore pixel, boundaries. Notice how there are no aliasing artifacts.

     Stroke with different screen scales after adjustment on a pixel boundary

    Adding a border

    Back to your app! The cells begin to look good, but you should add another touch to really make them stand out. This time, draw a bright yellow frame around the edges of the cell.

    You already know how to easily fill rectangles. Well, coats around them are just as easy.

    StarshipsListCellBackground.swift and add the following to the bottom of draw (_ :) :

      la 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 stretching that is input from the background rectangle with 4.5 points in both the x and y directions. Then, set the stroke color to yellow, the line width to one point, and finally hit the rectangle. Build and run your project.

     Far away (borders)

    Now your star chart list looks like it comes from a galaxy far away!

    Building a Card Setup [19659012] While the master display controller looks good, the detail viewer still needs some sprucing up!

     Detail View, Starts vs. Finish

    For this view, begin by drawing a gradient on the background image display using a custom UITableView subclass.

    Create a new Swift File called StarshipTableView.swift . Replace the generated code with the following:

      import UIKit
    
    class StarshipTableView: UITableView {
    override func draw (_ rect: CGRect) {
    vakt la kontekst = UIGraphicsGetCurrentContext () otherwise {
    return
    }
    
    let backgroundRect = limits
    context.drawLinearGradient (
    i: backgroundRect,
    startingWith: UIColor.starwarsSpaceBlue.cgColor,
    finishing with: UIColor.black.cgColor
    )
    }
    }
    

    This should start to be known now. In draw the method in your new table view, you get the current CGContext and draw a gradient in the boundaries of the view, from blue top and header in black at the bottom. Single!

    Open Main.storyboard and click TableView in Detail stage. In the identity inspector, set the class to your new StarshipTableView .

     Using Star Ship Table View

    Build and run the app, then press X-wing

      Detail display speed background

    Your detailed views now have a nice full-screen gradient that goes from top to bottom, but the cells in the table view make the best parts of the screen visible. effect. Time to fix this and add a little more flair to retail cells.

    Back in Main.storyboard select FieldCell in the detail in . In the Property inspector, set the background to Clear Color . Then open DetailViewController.swift and at the bottom of tableView (_: cellForRowAt :) before returning the cell, add the following:

      cell.textLabel! .textColor = .starwarsStarshipGrey
    cell.detailTextLabel! .textColor = .starwarsYellow
    

    This simply adds the cell's field name and value to more appropriate colors for your Stars Wars theme.

    Then add the following method to style the table by tableView (_: cellForRowAt :) see header:

      override func tableView (
    _ tableView: UITableView,
    willDisplayHeaderView view: UIView,
    forSection section: Int
    ) {
    view.tintColor = .starwarsYellow
    if let header = look like? UITableViewHeaderFooterView {
    header.textLabel? .textColor = .starwarsSpaceBlue
    }
    }
    

    Here you set the color of the table view's header to the theme yellow, giving it a yellow background, and its text color to the theme blue.

    Drawing lines

    As a final little bling, you will add a splitter to each cell in the detail view. Create a new Swift file this time called YellowSplitterTableViewCell.swift . Replace the generated code with the following:

      import UIKit
    
    class YellowSplitterTableViewCell: UITableViewCell {
    override func draw (_ rect: CGRect) {
    vakt la kontekst = UIGraphicsGetCurrentContext () otherwise {
    return
    }
    
    la y = bounds.maxY - 0.5
    la minX = bounds.minX
    la maxX = bounds.maxX
    
    context.setStrokeColor (UIColor.starwarsYellow.cgColor)
    context.setLineWidth (1.0)
    context.move (to: CGPoint (x: minX, y: y))
    context.addLine (to: CGPoint (x: maxX, y: y))
    context.strokePath ()
    }
    }
    

    YellowSplitterTableVIewCell uses Core Graphics to stretch a line at the bottom of the cell boundaries. Notice how the y value used is half a point less than the limits of the display to ensure that the splitter is fully retracted within the cell.

    Now you have to actually draw the line that shows the splitter.

    To Draw a line between A and B, you first move to point A, which will not cause Core Graphics to draw anything. You then add a line to point B which places the line from point A to point B in the context. You can then call strokePath () to touch the line.

    Finally open Main.storyboard again and set the class to FieldCell in Detail scene to be your newly created YellowSplitterTableViewCell using Identity inspector. Build and run your app. Then open the X-wing detail view.

    You can download the final project using the link Download materials at the top or bottom of this page. [19659000] [19659000]   Finished starship detail

    Where to go from here? training.

    The download also includes two playgrounds. RedBluePlayground.playground contains the example set in the context saving / recovery section and ClippedBorderedView.playground demonstrates clipping a boundary unless there is input.

    Additionally DemoProject DemoProject ] is a complete Xcode project that extends just across a one-point grid. This project is written in Objective-C so you can run it without changing on non-retina devices such as iPad 2, which requires iOS 9, to see the anti-aliasing effects for yourself. But don't panic! It's easy to understand now, you know the Core Graphics Swift API. :]

    At this time, you should be familiar with some pretty cool and powerful techniques with Core Graphics: fill and stretch rectangles, drawing lines and gradients, and clipping paths. Not to mention the table view, you look pretty cool. Congratulations!

    If this tutorial was a bit difficult to follow, or you want to be sure to cover the basics, check out the video Starting Core Graphics.

    If you're looking for something more advanced, take a look at the Intermediate Core Graphics course.

    Og hvis du ikke føler at du kan forplikte seg til et helt kurs, kan du prøve Core Graphics Article Series hvor Du lærer å tegne en hel app, inkludert grafer, fra grunnen av med Core Graphics!

    I tillegg er det mange flere Core Graphics-opplæringsprogrammer, alle nylig oppdatert for Xcode 10, på nettstedet.

    Hvis du har noen spørsmål eller kommentarer, vennligst bli med i forumdiskusjonen nedenfor.


Source link