Chapter 5 in Swift (including previous challenges)


#1

Please let me know any improvements I could make!

AppDelegate.swift

[code]//
// AppDelegate.swift
// Hypnosister
//
// Created by adam on 6/13/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    // Override point for customization after application launch.
    
    // Create CGRects for frames
    var screenRect = self.window!.bounds
    var bigRect = screenRect
    bigRect.size.width *= 2.0
    //bigRect.size.height *= 2.0
    
    // Create a screen-sized scroll view and add it to the window
    // (used for example of paging)
    let scrollView = UIScrollView(frame: screenRect)
    // Paging works by taking the size of a scroll view's "bounds" and
    // dividing up the "contentSize" it displays into sections of the same 
    // size. When the user pans, the view port scrolls to show only one of
    // the sections
    scrollView.pagingEnabled = true
    self.window!.addSubview(scrollView)
    
    /*
    // Create a super-sized hypnosis ciew and add it to the scroll view
    // (used for example of panning)
    let hypnosisView = HypnosisView(frame: bigRect)
    scrollView.addSubview(hypnosisView)
    */
    
    // Create a screen-sized hypnosis view and add it to the scroll view
    // (used for example of paging)
    let hypnosisView = HypnosisView(frame: screenRect)
    scrollView.addSubview(hypnosisView)
    
    // Add a second screen-sized hypnosis view just off the screen to the right
    screenRect.origin.x += screenRect.size.width
    let anotherHypnosisView = HypnosisView(frame: screenRect)
    scrollView.addSubview(anotherHypnosisView)
    
    
    // Tell the scroll view how big its content area is
    scrollView.contentSize = bigRect.size
    
    
    // intialize the HypnosicView with size of firstFrame, set bg to red
    // the values are in points; not pixels. retina display: point = .5px, on non-retina: point = 1px
    // frame is relative to the super view, in this case window
    //let firstFrame = CGRectMake(160, 240, 100, 150)
    /*
    let firstFrame: CGRect = self.window!.bounds
    let firstView: HypnosisView = HypnosisView(frame: firstFrame)
    // adds the HypnosisView as a subview of the window to make it part of the view hierarchy
    self.window!.addSubview(firstView)
    */
    
    /*
    // create a new HypnosisView, make it a subview of the first Hyposis view
    // frame is relative to the super view, in this case "firstView"
    let secondFrame = CGRectMake(20, 30, 50, 50)
    let secondView = HypnosisView(frame: secondFrame)
    secondView.backgroundColor = UIColor.blueColor()
    firstView.addSubview(secondView)
    */
    
    
    
    self.window!.backgroundColor = UIColor.whiteColor()
    self.window!.makeKeyAndVisible()
    return true
}

// …
}
[/code]

HypnosisView.Swift

[code]//
// HypnosisView.swift
// Hypnosister
//
// Created by adam on 6/13/14.
// Copyright © 2014 Adam Schoonmaker. All rights reserved.
//

import UIKit

class HypnosisView: UIView {

var circleColor = UIColor.lightGrayColor()


func setCircleColor(circleColor color: UIColor) {
    
    self.circleColor = color
    self.setNeedsDisplay()
}


init(frame: CGRect) {
    super.init(frame: frame)
    // Initialization code
    
    // All HypnosisViews start with a clear background color
    // (a view's backgroundColor is drawn regardless of what drawRect does,
    // so often custom views are set to have a transparent background so that
    // only the results of drawRect show
    self.backgroundColor = UIColor.clearColor()
}


// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
    // First thing to do when overriding this: get the bounds
    let bounds = self.bounds
    
    // Figure out the center of the bounds rectangle
    let center = CGPoint(x: (bounds.origin.x + bounds.size.width / 2.0),
        y: (bounds.origin.y + bounds.size.height / 2.0))
    
    // The largest circle will circumscribe the view
    let maxRadius = CGFloat(hypot(Double(bounds.size.width), Double(bounds.size.height)) / 2.0)

    // Draw the circle using UIBezierPath; draws lines and curves to make shapes
    let path = UIBezierPath()
    
    // Could have multiple instances of UIBezierPath, each one being one circle, 
    // or add multiple circles to a single instance. slightly more effcient w/ single instance
    for var currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20 {
        
        // "pick up" the "pencil" and move it to the correct starting spot
        path.moveToPoint(CGPointMake(center.x + currentRadius, center.y))
        
        path.addArcWithCenter(center, radius: currentRadius, startAngle: 0,
            endAngle: CGFloat(M_PI * 2.0), clockwise: true)

        //println("drawing circle... x:\(center.x)   y:\(center.y)   radius:\(currentRadius)")
    }

    
    // Make the line width 10 points
    path.lineWidth = 10
    
    // Sets the color of subsequent stroke operations to the color that the receiver represents
    circleColor.setStroke()
    
    // Draw the line!
    path.stroke()
    

    // Shadows cannot be "unset", so the current context must be saved and
    // restored from after the shawdow is used
    var baseContext = UIGraphicsGetCurrentContext()
    CGContextSaveGState(baseContext)
    
    // Calculate values to use for drawing the logo
    let logoRectWidth = bounds.size.width / 2.0
    let logoRectHeight = bounds.size.height / 2.0
    let logoRectOriginX = logoRectWidth / 2.0
    let logoRectOriginY = logoRectHeight / 2.0
    
    
    
    // Draw a triangle path using the logo calculated values
    let triangleTop = CGPointMake(logoRectWidth, logoRectOriginY - 20.0)
    let triangleBottomLeft = CGPointMake(logoRectOriginX - 10.0,
        logoRectOriginY + logoRectHeight + 20.0)
    let triangleBottomRight = CGPointMake(logoRectOriginX + logoRectWidth + 10.0,
        logoRectOriginY + logoRectHeight + 20.0)
    let triangleBottomMiddle = CGPointMake(logoRectWidth,
        logoRectOriginY + logoRectHeight + 20.0)
    
    // Set "pencil" down at the top of the triangle, then draw the edges
    let trianglePath = UIBezierPath()
    trianglePath.moveToPoint(triangleTop)
    trianglePath.addLineToPoint(triangleBottomLeft)
    trianglePath.addLineToPoint(triangleBottomRight)
    trianglePath.addLineToPoint(triangleTop)
    //trianglePath.stroke()
    
    // Use the triangle path to draw a gradient
    // Gradients cover everything in the view, so a clipping path must be 
    // installed on the graphics context that defines what the gradient covers
    trianglePath.addClip()
    
    // Create the gradient
    let locations: CGFloat[] = [0.0, 1.0]
    let components: CGFloat[] = [ 0.0, 1.0, 0.0, 1.0, // Start color: green
                                    1.0, 1.0, 0.0, 1.0] // End color: yellow
    var colorspace = CGColorSpaceCreateDeviceRGB()
    var gradient = CGGradientCreateWithColorComponents(colorspace, components,
        locations, 2)
    
    // Draw the gradient
    CGContextDrawLinearGradient(baseContext, gradient, triangleTop,
        triangleBottomMiddle, 0)
    
    // Deallocate (note: make sure gradient and colorspace are defined with
    // "var" instead of "let", or else this will give an error
    //CGGradientRelease(gradient)
    //CGColorSpaceRelease(colorspace)
    
    // Restore the graphics state to clear the clip path
    CGContextRestoreGState(baseContext)

    
    
    baseContext = UIGraphicsGetCurrentContext()
    CGContextSaveGState(baseContext)
    // Now anything drawn will appear with a shadow
    CGContextSetShadow(baseContext, CGSizeMake(4, 7), 3)
    
    // Create UIImage object from logo.png imported file, draw it over circles
    let logoImage = UIImage(named: "logo.png")
    
    logoImage.drawInRect(CGRect(x: logoRectOriginX, y: logoRectOriginY,
        width: logoRectWidth, height: logoRectHeight))
    
    // Now anything drawn won't have a shadow
    CGContextRestoreGState(baseContext)
}


// When a finger touches the view; this is a touch event handler
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
    
    println("\(self) was touched")
    
    // Get 3 random numbers between 0 and 1
    let red = Double(arc4random() % 100) / 100.0
    let green = Double(arc4random() % 100) / 100.0
    let blue = Double(arc4random() % 100) / 100.0
    
    let randomColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0)
    
    setCircleColor(circleColor: randomColor)
}

}
[/code]


#2

Hi, I’m tring the same implementation in Swift
Doesn’t this BNRHypnosisView.swift give an compilation error ‘NSNumber’ is not a subtype of ‘CDouble’ at the line below?

// The largest circle will circumscribe the view let maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0


#3

For some strange reason the code you are talking about worked until earlier this morning.

Anyway, I updated my first post above with a change or two because of that, and specifically changed that line to


#4

it worked fine. thank you


#5

To carry on the getter/setter pattern to Swift you can do this:

var circleColor : UIColor = UIColor.lightGrayColor() { didSet { setNeedsDisplay() } }
also you can use trianglePath.closePath() for the final side of the triangle.


#6

Hey guys,

I’ve been working through the book in Swift too, and I’m swing some strange behavior in the latest Xcode 6 beta (3) – would either of you be able to build your programs and see if they still render properly?

More specifically, I can’t seem to get the second HypnosisView to render where it should. The frame’s CGRect has the correct origin.x when the app is loading – a breakpoint shows it’s got the correct value (320), but when the view actually loads in the simulator, it shows up half way across the screen – I’ll attach a screen shot. I also notice that any other way I manipulate this value, it responds properly – except the one I want.

I’m starting to think I’m crazy, so I really appreciate it if you could run the program against the latest beta and see if you experience this as well! If it turns out to be a genuine bug, I’ll go ahead and report it. Thanks guys.

Page 2 of the scroll view:


#7

When creating the random color in Swift, I’m getting an error that I think is a bug.

       // Get 3 random numbers between 0 and 1
        let red = Double(arc4random() % 100) / 100.0
        let green = Double(arc4random() % 100) / 100.0
        let blue = Double(arc4random() % 100) / 100.0
        
        //create random color 
        let randomColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0)

The error is reading extra argument green in call, even though I know this needs to be supplied. Any one else run into this? I think it is an Xcode bug.


#8

[quote=“screamingeagle39”]When creating the random color in Swift, I’m getting an error that I think is a bug.

       // Get 3 random numbers between 0 and 1
        let red = Double(arc4random() % 100) / 100.0
        let green = Double(arc4random() % 100) / 100.0
        let blue = Double(arc4random() % 100) / 100.0
        
        //create random color 
        let randomColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0)

The error is reading extra argument green in call, even though I know this needs to be supplied. Any one else run into this? I think it is an Xcode bug.[/quote]

I remember hitting this when I was working on that chapter, and the compiler is giving you no help at all with that error message. It’s actually a type issue with the UIColor init function – take a look at the documentation on what type UIColor is expecting – it’s actually CGFloat, not Double, and for whatever reason (that is above my understanding of llvm) it assumes the wrong initializer you’re trying to use.


#9

I too am going through this book in swift (i thought i was being original, but this post is very helpful, so i’m glad i wasn’t :slight_smile:)
I seem to be stuck on this part. Xcode will not allow me to do a simple “init” in BNRHypnosisView like you have in your post. It forces me to use the override keyword, and add another “required init” method that takes an NSCoder as the parameter.here is my init code:

required init(coder aDecoder: NSCoder) { super.init() } override init(frame: CGRect) { super.init(frame: frame) self.circleColor = UIColor.lightGrayColor() opaque = false }

It runs fine for me like this, but i am wondering if it is causing a problem i am having when i try to implement the scrollview. When i try to add the scrollView, the program seems to create the hypnosis stuff tow times- once in the original size, and once in the double size. It also does not allow any scrolling. do you have any idea what could be causing this?