قالب وردپرس درنا توس
Home / IOS Development / Nuclear graphics: How to make a glossy button

Nuclear graphics: How to make a glossy button



Update Note : Lea Marolt Sonnenschein updated this guide for iOS 12, Xcode 10 and Swift 4.2. Ray Wenderlich wrote the original.

Core Graphics is one of the frameworks that is easy to avoid as an iOS developer. It's a bit unclear, the syntax isn't super modern, and Apple doesn't give that much love to WWDC as they should! In addition, you can avoid it very easily by just using pictures instead.

However, Core Graphics is an insanely powerful tool to master! You can free yourself from the chess of graphic designers and use the mighty CG sword to create stunning UI beauty on your own.

In this tutorial, you learn how to create a customizable, reusable glossy button using Core Graphics only. Haven't you heard skeuomorphism back in style? ;]

In the process, you learn how to draw rounded rectangles, how to easily thin your Core Graphics drawings and how to create gradient and gloss effects.

There are many options for customizing UIButton s, from full customized UIButton classes to smaller extensions. But what has been missed in this discussion is a detailed Core Graphics guide on how to customize the buttons themselves, from start to finish. It's quite simple; You can use it to get the exact look you want in your app.

It's time to get started!

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the startup project.

Open the startup project: CoolButton Starter .

The project structure is very simple, consisting only of the files created when you select Single View App Xcode template. The only two major exceptions are:

  • A few pictures in the catalog Assets.xcassets .
  • You can find a preset user interface for ViewController in Main.storyboard .
  • Go to File ▸ New ▸ File … select iOS ▸ Cocoa Touch Class and click Next .

    In the next menu, enter CoolButton as the class name. Enter UIButton in the subclass field. For languages, select Swift . Click Next and then Create .

    Open CoolButton.swift and replace the class display with the following:

      class CoolButton: UIButton {
    was color tone: CGFloat {
    didSet {
    setNeedsDisplay ()
    }
    }
    
    heat measurement: CGFloat {
    didSet {
    setNeedsDisplay ()
    }
    }
    
    was brightness: CGFloat {
    didSet {
    setNeedsDisplay ()
    }
    }
    }
    

    Here you create three properties that you want to use to customize the color tone, saturation and brightness.

    When the properties are set, you trigger a call to setNeedsDisplay to force UIButton to pull the button when the user changes its color.

    Insert the following code at the bottom of CoolButton just before the last curl bracket:

      requires init? (Codes aDecoder: NSCoder) {
    self.hue = 0.5
    self.saturation = 0.5
    self.brightness = 0.5
    
    super.init (codes: aDecoder)
    
    self.isOpaque = false
    self.backgroundColor = .clear
    }
    
    override func draw (_ rect: CGRect) {
    vakt la kontekst = UIGraphicsGetCurrentContext () otherwise {
    return
    }
    
    la color = UIColor (hue: hue,
    saturation: saturation,
    brightness: brightness,
    alpha: 1.0)
    
    context.setFillColor (color.cgColor)
    context.fill (borders)
    }
    

    Here, you initialize the variables and fill the button with a preconfigured color to make sure everything works from the beginning.

    Configure the button's user interface

    Open Main.storyboard to configure the user interface. It is a view controller that contains three sliders – one for hue, one for saturation and one for brightness.

    You are missing UIButton so add it to the top of the screen. Go to Object Library type UIButton and drag it into the screen.

    Time for any auto setup! Check-drag from the button to the display and select Center horizontally in safe area .

    Check drag from the button to the hue and select Vertical Position

    .

    ] Resize the button to a size you want. Control-drag left or right from the button and select Width .

    Control-drag up or down from the button and select Height .

    Note : If you have trouble inserting width and height restrictions by dragging, select the button and click the button Add new contracts to the bottom right of the canvas. It looks like a Tie fighter from Star Wars.

    Then, the text that says "Button" is deleted by double-clicking it and pressing the Delete key.

    With the button still selected, go to Inspectors sidebar on the right side of the screen and click Identity inspector . In Custom Class asse Class enter CoolButton to make your button an instance of the CoolButton class.

    With the button in

    Open the buttons

    Open ViewController.swift and replace the class display with the following:

      class ViewController: UIViewController {
    @IBOutlet weak was cool button: CoolButton!
    @IBAction func hueValueChanged (_ sender: Any) {}
    @IBAction func saturationValueChanged (_ sender: Any) {}
    @IBAction func brightnessValueChanged (_ sender: Any) {}
    }
    

    Here you declare a reference to the button you just created in your storyboard. You also declare the callbacks that occur when the values ​​of the configuration slides change.

    Now, open Main.storyboard again and select Assistant Editor in the top bar to display ViewController.swift and Main.storyboard and Main.storyboard and Main.storyboard and side by side.

    Control-drag from the button in Display the Scene ▸ ViewController controller on the left and connect it to coolButton outlet.

    Similarly check-drag from the sliders in See the controller scene enen ViewController on the left and connect each to the appropriate value-changed callback on

    Next, switch to ViewController.swift and implement the valued recalls to the sliders:

      @IBAction func hueValueChanged (_ sender: Any) {
    guard let slider = sender as? UISlider else {return}
    coolButton.hue = CGFloat (slider.value)
    }
    
    @IBAction func saturationValueChanged (_ sender: Any) {
    guard let slider = sender as? UISlider else {return}
    coolButton.saturation = CGFloat (slider.value)
    }
    
    @IBAction func brightnessValueChanged (_ sender: Any) {
    guard let slider = sender as? UISlider else {return}
    coolButton.brightness = CGFloat (slider.value)
    }
    By default,  UISlider  s has a range of 0.0 to 1.0. This is perfect for hue, saturation and brightness values, which also range from 0.0 to 1.0, so you can just put them directly. 

    After all this work, Phew is finally time to build and run! If everything works well, you can play with the sliders to fill the button with different colors:

    Drawing rounded rectangles

    It is true that you can easily create square buttons, but button styles come and go faster than the weather is changing in Chicago!

    In fact, since we originally launched this tutorial, square buttons and rounded rectangle buttons have flipped back and forth to number one place in the button page, so it's a good idea to know how to create both versions.

    You can also claim that it is quite simple to make rounded rectangles by simply changing the corner radius of a UIView but where is it fun in it? There is so much more satisfaction, or perhaps madness, in doing it the hard way. :]

    One way to create rounded rectangles is to draw arcs with CGContextAddArc API. Using that API, you can draw a curved in each corner and draw lines to connect them. But it is cumbersome and requires a lot of geometry.

    Fortunately, it's an easier way! You don't have to do so much math and it works well with drawing rounded rectangles. It is CGContextAddArcToPoint API.

    Using CGContextAddArcToPoint API

    CGContextAddArcToPoint API lets you describe the arc to draw by specifying two tangent lines and a radius. The following chart from the Quartz2D Programming Guide shows how it works:

     CGContextAddArcToPoint Diagram "title =" CGContextAddArcToPoint Diagram "width =" 500 "height =" 365 "class =" alignnone size full-wp image-2139 limits " srcset = "https://koenig-media.raywenderlich.com/uploads/2010/09/CGContextAddArcToPoint.jpg 500w, https://koenig-media.raywenderlich.com/uploads/2010/09/CGContextAddArcToPoint-150x109. jpg 150w, https://koenig-media.raywenderlich.com/uploads/2010/09/CGContextAddArcToPoint-300x219.jpg 300w "sizes =" (max width: 500px) 100vw, 500px "/> </p>
<p>  When & # 39; re working with a rectangle, you know the tangent lines for each arc you want to draw - they are just the edges of the rectangle! And you can specify the radius based on how rounded you want the rectangle to be - the larger the arc, the more rounded corners will be. </p>
<p>  The other nice thing about this feature is that if the current point in the path is not set to where you tell the arc To begin drawing, it will draw a line from the current point to the beginning of the path. So you can use this as a shortcut to draw a rounded rectangle in just a few conversations. </p>
<h3>  Draw Your Arches </h3>
<p>  Since you are going to make a bunch of rounded rectangles in this Core Graphics tutorial and you want yours code to be as reusable as possible, create a separate file for al Smile your drawing methods. </p>
<p>  Go to <em> File ▸ New ▸ File ... </em> and select <em> iOS ▸ Swift File </em>. Press <em> Next </em> call it <em> Drawing </em> and click <em> Create </em>. </p>
<p>  Now replace the <code> import fund </code> with the following: </p>
<pre lang= import UIKit import CoreGraphics extension UIView { func createRoundedRectPath (for rect: CGRect, radius: CGFloat) -> CGMutablePath { let jobs = CGMutablePath () // 1 la midTopPoint = CGPoint (x: rect.midX, y: rect.minY) path.move (to: midTopPoint) // 2 la topRightPoint = CGPoint (x: rect.maxX, y: rect.minY) la bottomRightPoint = CGPoint (x: rect.maxX, y: rect.maxY) la bottomLeftPoint = CGPoint (x: rect.minX, y: rect.maxY) la topLeftPoint = CGPoint (x: rect.minX, y: rect.minY) // 3 path.addArc (tangent1End: topRightPoint, tangent2End: bottomRightPoint, radius: radius) path.addArc (tangent1End: bottomRightPoint, tangent2End: bottomLeftPoint, radius: radius) path.addArc (tangent1End: bottomLeftPoint, tangent2End: topLeftPoint, radius: radius) path.addArc (tangent1End: topLeftPoint, tangent2End: topRightPoint, radius: radius) // 4 path.closeSubpath () return path } }

    The above code creates a global extension for all UIView type so you can use it for more than just UIButton s.

    The code also instructs createRoundedRectPath ( for: radius :) to draw the rounded rectangle in the following order:

    <img src = "https://koenig-media.raywenderlich.com/uploads/2010/09/RoundedRect.jpg" alt = "How to draw a rounded rectangle with Core Graphics" title = "How to draw a rounded rectangle with Core Graphics" width = "544" height = "219" class = "alignnone size-full wp-image-2140 bordered" srcset = "https : //koenig-media.raywenderlich.com/uploads/2010/09/RoundedRect.jpg 544w, https: //koenig-media.raywenderlich.com/uploads/2010/09/RoundedRect-150x60.jpg 150w, https: / /koenig-media.raywenderlich.com/uploads/2010/09/RoundedRect-300x120.jpg 300w "sizes

  • You declare every corner as a local constant.
  • You add each a rc to the path:
    1. First, add a curved top right. Before drawing an arc, CGPathAddArcToPoint will draw a line from the current position in the middle of the rectangle to the beginning of the arc for you.
    2. Similarly, add an arc to the lower right corner and the link line.
    3. Then add an arc to the lower left corner and the connection line.
    4. Last, add a curved top left corner and connection line.
  • Finally, connect the end point to the arc starting with closeSubpath () .
  • Make your button rounded

    OK, now is the time to put this method to work! Open CoolButton.swift and replace draw (_ :) with the following:

      override func draw (_ rect: CGRect) {
    vakt la kontekst = UIGraphicsGetCurrentContext () otherwise {
    return
    }
    
    // 1
    la outerColor = UIColor (
    hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
    leave shadowColor = UIColor (red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)
    
    // 2
    la outer margin: CGFloat = 5.0
    la outerRect = rect.insetBy (dx: outerMargin, dy: outerMargin)
    // 3
    let outerPath = createRoundedRectPath (for: outerRect, radius: 6.0)
    
    // 4
    whose state! = .highlighted {
    context.saveGState ()
    context.setFillColor (outerColor.cgColor)
    context.setShadow (offset: CGSize (width: 0, height: 2),
    blur: 3.0, color: shadowColor.cgColor)
    context.addPath (outerPath)
    context.fillPath ()
    context.restoreGState ()
    }
    }
    

    To break this down:

    1. You define your two colors.
    2. Then use insertBy (dx: dy :) to get a slightly smaller rectangle (5 pixels on each page) where you want to draw the rounded line. You've made it smaller so you have room to draw a shade on the outside.
    3. Then you call the function you just wrote, createRoundedRectPath (for: radius :) to create
    4. Finally, enter the fill color and shadow, add the path to your context and call fillPath ( ) to fill it with your current color.

    Note : You will only run the code if the button is not currently highlighted; for example, it is not drained.

    Build and run the app; If everything works well, you should see the following:

    Adding a gradient

    Ok, the button starts to look pretty good, but you can do even better! How about adding a gradient?

    Add the following function to Drawing.swift to make it universally available to anyone UIView :

      func drawLinearGradient (
    context: CGContext, straight: CGRect, startColor: CGColor, endColor: CGColor) {
    // 1
    la colorSpace = CGColorSpaceCreateDeviceRGB ()
    
    // 2
    la colorLocations: [CGFloat] = [0.0, 1.0]
    
    // 3
    let colors: CFArray = [startColor, endColor] as CFArray
    
    // 4
    la gradient = CG gradient (
    colorsSpace: colorSpace, colors: colors, locations: colorLocations)!
    
    // More ...
    }
    

    It doesn't look like much, but much happens in this feature!

    1. The first thing you need is to get a color space that you want to use to draw the gradient.
    2. Note : There is a lot you can do with color sites, but 99% of the time you just want a standard device-dependent RGB color space. So just use the function CGColorSpaceCreateDeviceRGB () to get the reference you need.

    3. Then, set up an array that tracks the location of each color within the gradient. A value of 0 means the start of the gradient, 1 means the end of the gradient. You only have two colors and you want the first one to be at the start and the other to be at the end so you pass 0 and 1.
    4. 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. This can be useful for certain effects.

    5. After that, you create a matrix with the colors you passed into your function. You use a regular old selection here to make it easier, but you have to throw it as a CFArray since it is the API that requires.
    6. Then make your gradient with CGGradient (colorsSpace: colors: places :) passes in the color space, color scheme and places you have previously created.

    You now have a gradient reference, but it hasn't pulled anything yet. There is only one point to the information you want to use when you actually draw it later.

    Complete the function by adding the following at the end of drawLinearGradient (context: rect: startColor: endColor :) :

      // 5
    la startPoint = CGPoint (x: rect.midX, y: rect.minY)
    la endPoint = CGPoint (x: rect.midX, y: rect.maxY)
    
    context.saveGState ()
    
    // 6
    context.addRect (rect)
    // 7
    context.clip ()
    
    // 8
    context.drawLinearGradient (
    gradient, start: startPoint, end: endPoint, options: [])
    
    context.restoreGState ()
    
    1. The first thing you do is calculate the start and end points where you want to draw the gradient. You just specify this as a line from the "top center" to the "bottom center" of the rectangle.

    The rest of the code helps you draw a gradient in the specified rectangle, the key function is drawLinearGradient (

    Limitation of a gradient to a subarea

    The strange thing about this feature is that it fills the entire drawing area with the gradient - There is no way to set it to just fill a sub-area with the gradient!

    Well, without clipping, that is!

    Clipping is a wonderful feature of Core Graphics that allows you to limit drawing All you have to do is add the shape of the context, but instead of filling it as you normally would, call clip () . Now you have limited all future drawing to that region!

    So that's what you do here:

    1. You add the rectangle to the context.
    2. Cut to that region
    3. Then call

    So what is this about saveGState () / restoreGState () is about?

    Well, Core Graphics is a state machine. You configure a set of states you want, such as colors and line thickness, and then perform actions to actually draw them. It means that once you have set something, it stays that way until you replace it.

    Well, you've just cut to a region, so unless you do something about it, you'll never be able to draw outside this region again!

    This is where saveGState () / restoreGState () comes to the rescue.

    With these you can save the current layout of your context to a stack and then pop it back later when you have finished coming back to where you were.

    That's it, try now!

    Open CoolButton.swift and add this to the bottom of draw (_ :) :

      // Outer course Gradient:
    // 1
    la outerTop = UIColor (hue: hue, saturation: saturation,
    brightness: brightness, alpha: 1.0)
    la outerBottom = UIColor (hue: hue, saturation: saturation,
    brightness: brightness * 0.8, alpha: 1.0)
    
    // 2
    context.saveGState ()
    context.addPath (outerPath)
    context.clip ()
    drawLinearGradient (context: context, stretch: outerRect,
    startColor: outerTop.cgColor, endColor: outerBottom.cgColor)
    context.restoreGState ()
    

    Build and run; You should see something like this:

    1. First, define top and bottom colors.
    2. Then, draw the gradient by saving the current graphics status on the stack, adding your path, cutting it, drawing the gradient, and restoring the state again.

    Saturday, your button looks pretty snazzy! What about some extra pizazz ?!

    Adding a Glossy Effect

    Now it's time to make this button shiny, because skeuomorphism should never have gone out of style!

    When adding a glossy effect to a button in Core Graphics, things can become quite complicated. If you feel hardcore, check out some great work by Matt Gallagher and Michael Heyeck in the case.

    But to my bad eyes you can get a pretty nice approach to a shine effect just by using a gradient alpha mask, which is much easier to understand and code. So you have to go with it.

    This is something you can apply for UIView s across the board, so add the following feature to the extension UIView in Drawing.swift :

      func drawGlossAndGradient (
    context: CGContext, straight: CGRect, startColor: CGColor, endColor: CGColor) {
    
    // 1
    drawLinearGradient (
    context: context, rect: straight, startColor: startColor, endColor: endColor)
    
    la glossColor1 = UIColor (red: 1.0, green: 1.0, blue: 1.0, alpha: 0.35)
    la glossColor2 = UIColor (red: 1.0, green: 1.0, blue: 1.0, alpha: 0.1)
    
    let topHalf = CGRect (origin: rect.origin,
    size: CGSize (width: straight width, height: straight.height / 2))
    
    drawLinearGradient (context: context, stretch: topHalf,
    startColor: glossColor1.cgColor, endColor: glossColor2.cgColor)
    }
    

    This feature basically draws a gradient over a rectangle from one start to the end color, and then adds a gloss to the top half. Here's an overview of what's happening:

    1. To draw the degree, you are called the function you wrote earlier.
    2. To draw the gloss, draw another gradient beyond it, from quite transparent (white with 0.35 alpha) to very transparent (white with 0.1 alpha).

    Simple, eh? Connect it and see how it looks. Return to CoolButton.swift and make a small change to draw (_ :) . Replace this line, which is the second to last in draw (_ :) :

      drawLinearGradient (context: context, rect: outerRect,
    startColor: outerTop.cgColor, endColor: outerBottom.cgColor)
    

    with:

      drawGlossAndGradient (context: context, rect: outerRect,
    startColor: outerTop.cgColor, endColor: outerBottom.cgColor)
    

    If you can't detect the difference, you've just changed drawLinearGradient (context: rect: startColor: endColor :) into drawGlossAndGradient (context: rect: startColor: endColor :) the newly added method in Drawing.swift .

    Build and run, and your button should look like this:

    Oooh, shiny

    Styling the Button

    Now for the super fine, nit-demanding details. If you make a 3D button, you might as well go all out. To do that, you need a slant.

    To create an oblique effect, add an inner path that has a slightly different gradient than the outer path. Add this to the bottom of draw (_ :) in CoolButton.swift :

      // 1: Inner colors
    la innerTop = UIColor (
    hue: hue: saturation, brightness: brightness * 0.9, alpha: 1.0)
    leave innerBottom = UIColor (
    hue: hue, saturation: saturation, brightness: brightness * 0.7, alpha: 1.0)
    
    // 2: Inner road
    la innerMargin: CGFloat = 3.0
    let innerRect = outerRect.insetBy (dx: innerMargin, you: innerMargin)
    leave innerPath = createRoundedRectPath (for: innerRect, radius: 6.0)
    
    // 3: Draw the inner lane gloss and gradient
    context.saveGState ()
    context.addPath (innerPath)
    context.clip ()
    drawGlossAndGradient (context: context,
    straight: innerRect, startColor: innerTop.cgColor, endColor: innerBottom.cgColor)
    context.restoreGState ()
    

    Here you shrink the rectangle again with insertBy (dx: dy :) then you get a rounded rectangle and run a gradient over it. Build and run, and you'll see a subtle improvement:

    Highlighting the button

    Your button looks pretty cool, but it doesn't seem like a button. There is no indication whether the user has pressed the button or not.

    To deal with this, you must override the touch events to tell your button to show itself, as it may need an update after a user has selected it.

    Add the following in CoolButton.swift :

      @objc func hesitateUpdate () {
    setNeedsDisplay ()
    }
    
    override func touchesBegan (_ touch: Set  with event: UIEvent?) {
    super.touchesBegan (touches, with: arrangement)
    setNeedsDisplay ()
    }
    
    override func touchesMoved (_ touch: Set  with event: UIEvent?) {
    super.touchesMoved (touches, with: arrangement)
    setNeedsDisplay ()
    }
    
    override func touchesCancelled (_ touches: Set  with event: UIEvent?) {
    super.touchesCancelled (touches, with: arrangement)
    setNeedsDisplay ()
    
    perform (#selector (hesitateUpdate), with: null, afterDelay: 0.1)
    }
    
    override func touchesEnded (_ touch: Set  with event: UIEvent?) {
    super.touchesEnded (touches, with: arrangement)
    setNeedsDisplay ()
    
    perform (#selector (hesitateUpdate), with: null, afterDelay: 0.1)
    }
    

    Build and run the project and you will see that there is a difference when you press the button now - the highlight and slope disappear.

    But you can make the effect a little better right away drawing (_ :) :

    When a user presses the button, the general button must be darker.

    You can accomplish this by creating a temporary brightness variable called actualBrightness and then adjusting it correctly based on the state of the button:

      was actualBrightness = brightness
    
    if the condition == .highlighted {
    actualBrightness - = 0.1
    }
    

    Then, in draw (_ :) replace all cases of brightness with actualBrightness .

    Together _ :) the function now looks like this. It's a bit long, but the repetition is worth it:

      override func draw (_ rect: CGRect) {
    vakt la kontekst = UIGraphicsGetCurrentContext () otherwise {
    return
    }
    
    was actualBrightness = brightness
    
    if the condition == .highlighted {
    actualBrightness - = 0.1
    }
    
    la outerColor = UIColor (
    hue: hue, saturation: saturation, brightness: actualBrightness, alpha: 1.0)
    leave shadowColor = UIColor (red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)
    
    la outer margin: CGFloat = 5.0
    la outerRect = rect.insetBy (dx: outerMargin, dy: outerMargin)
    let outerPath = createRoundedRectPath (for: outerRect, radius: 6.0)
    
    whose state! = .highlighted {
    context.saveGState ()
    context.setFillColor (outerColor.cgColor)
    context.setShadow (
    offset: CGSize (width: 0, height: 2), blur: 3.0, color: shadowColor.cgColor)
    context.addPath (outerPath)
    context.fillPath ()
    context.restoreGState ()
    }
    
    // Outer Path Gloss & Gradient
    la outerTop = UIColor (hue: hue, saturation: saturation,
    brightness: actualBrightness, alpha: 1.0)
    la outerBottom = UIColor (hue: hue, saturation: saturation,
    brightness: actualBrightness * 0.8, alpha: 1.0)
    
    context.saveGState ()
    context.addPath (outerPath)
    context.clip ()
    drawGlossAndGradient (context: context, stretch: outerRect,
    startColor: outerTop.cgColor, endColor: outerBottom.cgColor)
    context.restoreGState ()
    
    // Interior Path Gloss & Gradient
    let innerTop = UIColor (hue: hue, saturation: saturation,
    brightness: actualBrightness * 0.9, alpha: 1.0)
    let innerBottom = UIColor (hue: hue, saturation: saturation,
    brightness: actualBrightness * 0.7, alpha: 1.0)
    
    la innerMargin: CGFloat = 3.0
    let innerRect = outerRect.insetBy (dx: innerMargin, you: innerMargin)
    leave innerPath = createRoundedRectPath (for: innerRect, radius: 6.0)
    
    context.saveGState ()
    context.addPath (innerPath)
    context.clip ()
    drawGlossAndGradient (context: context, rect: innerRect,
    startColor: innerTop.cgColor, endColor: innerBottom.cgColor)
    context.restoreGState ()
    }
    

    Build and run; now the button should look pretty good when you press it!

    Where to go from here?

    Now that you've gone through all the steps to create custom buttons from scratch, you should be well aware of how to customize each aspect of the button to your taste for the project's style!

    Hopefully, this tutorial helped you to become more comfortable with Core Graphics and triggered an interest in further exploring the API. :]

    If this tutorial was a bit difficult to follow, or you want to make sure you cover the basics, see our Beginning Core Graphics video series.

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

    And if you don't feel like you can commit to a whole course, try the Core Graphics Article Series where you learn to draw an entire app, including graphs, from scratch with Core Graphics!

    In addition, there are many more Core Graphics tutorials, all recently updated for Xcode 10, on the site.

    If you have any questions or comments, please join the forum discussion below.


Source link