0

What i want to do seems very simple to me, but I can't figure out a way to implement it given that I am pretty new to iOS.

I want to pretty much have my background a gradient that changes colors by fading in and fading out very slowly.

I found this on github and used it to create two gradient nodes with different colors, the only issue is that I can't seem to animate its alpha or have it fade in and out in any way. https://github.com/braindrizzlestudio/BDGradientNode Here is what I did:

//Blueish
    let color1 = UIColor(red: 0.965, green: 0.929, blue: 0.667, alpha: 1)
    let color2 = UIColor(red: 0.565, green: 0.71, blue: 0.588, alpha: 1)
    let color3 = UIColor(red: 0.259, green: 0.541, blue: 0.529, alpha: 1)
    let colors = [color1, color2, color3]

    //Redish
    let color4 = UIColor(red: 1, green: 0.518, blue: 0.769, alpha: 1)
    let color5 = UIColor(red: 0.859, green: 0.22, blue: 0.541, alpha: 1)
    let color6 = UIColor(red: 0.737, green: 0, blue: 0.314, alpha: 1)
    let colors2 = [color4, color5, color6]

    let blending : Float = 0.3

    let location1 : CGFloat = 0.5
    let locations : [CGFloat] = [location1]

    let startPoint = CGPoint(x: 0.3, y: 0.0)
    let endPoint = CGPoint(x: 0.6, y: 0.8)

    let size = CGSize(width: self.size.width, height: self.size.height)

    let texture = SKTexture(imageNamed: "White Background")


    myGradientNode = BDGradientNode(linearGradientWithTexture: texture, colors: colors, locations: nil, startPoint: startPoint, endPoint: endPoint, blending: blending, keepTextureShape: true, size: size)

    myGradientNode2 = BDGradientNode(linearGradientWithTexture: texture, colors: colors2, locations: nil, startPoint: startPoint, endPoint: endPoint, blending: blending, keepTextureShape: true, size: size)
    myGradientNode2?.blending = 0

    self.addChild(myGradientNode2!)
    self.addChild(myGradientNode!)

I feel like there is an easier way, maybe not using this. You see this in a lot of gaming apps now and it would help so much if someone could assist me with achieving this stunning design!

OriginalAlchemist
  • 391
  • 1
  • 7
  • 28

2 Answers2

3

Here is a possible complete solution (in Swift 2.2), which you can tweak to your heart's content (including error handling that makes sense for your app). I'll lay out the code in reverse order, starting with the goal of being able to write:

// assuming:
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)

if let bg = HorizontalGradientNode(
    size: scene.size,
    fadingFrom: [.AliceBlue, .BurlyWood],
    to: [.AliceBlue, .CornflowerBlue],
    wait: 1,
    duration: 2)
{
    scene.addChild(bg)
}

This will add a node to the scene encapsulating two child gradient sprite nodes, the top one of which will fade in and out forever. The HorizontalGradientNode.init takes two arrays of CGColor. They can contain two or more colours each! The named CGColors (AliceBlue, CornflowerBlue, etc.) can be constructed in the following way:

public extension CGColor {

    public static func withHex(hex: Int, alpha: CGFloat = 1) -> CGColor {
        let x = max(0, min(hex, 0xffffff))
        let r = CGFloat((x & 0xff0000) >> 16)   / 0xff
        let g = CGFloat((x & 0x00ff00) >> 8)    / 0xff
        let b = CGFloat( x & 0x0000ff)          / 0xff
        return CGColorCreateGenericRGB(r, g, b, alpha)
    }

    public static var AliceBlue: CGColor { return .withHex(0xF0F8FF) }
    public static var BurlyWood: CGColor { return .withHex(0xDEB887) }
    public static var CornflowerBlue: CGColor { return .withHex(0x6495ED) }
    // see named colours at: http://www.w3schools.com/colors/colors_names.asp
}

The simplest version of HorizontalGradientNode would be something like:

class HorizontalGradientNode : SKNode {

    init?(size: CGSize, fadingFrom colors1: [CGColor], to colors2: [CGColor], wait: NSTimeInterval, duration: NSTimeInterval) {
        guard
            let grad1 = CGImage.withHorizontalGradient(size: size, colors: colors1),
            let grad2 = CGImage.withHorizontalGradient(size: size, colors: colors2)
            else
        {
            return nil
        }
        let bg1 = SKSpriteNode(texture: SKTexture(CGImage: grad1))
        let bg2 = SKSpriteNode(texture: SKTexture(CGImage: grad2))
        bg2.alpha = 0

        super.init()
        addChild(bg1)
        addChild(bg2)

        bg2.runAction(
            .repeatActionForever(
                .sequence(
                    [
                        .waitForDuration(wait),
                        .fadeInWithDuration(duration),
                        .waitForDuration(wait),
                        .fadeOutWithDuration(duration)
                    ]
                )
            )
        )
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

This CGImage.withHorizontalGradient(size:, colors:) could be implemented as follows:

public extension CGImage {

    public static func withHorizontalGradient(size size: CGSize, colors: [CGColor]) -> CGImage? {
        guard colors.count >= 2 else { return nil }
        let locations: [CGFloat] = colors.indices.map{
            CGFloat($0) / CGFloat(colors.count - 1)
        }
        guard let gradient = CGGradientCreateWithColors(nil, colors, locations) else {
            return nil
        }
        let start = CGPoint(x: size.width / 2, y: size.height)
        let end = CGPoint(x: size.width / 2, y: 0)
        return CGContext.rgb(size)?
            .draw(linearGradient: gradient, start: start, end: end)
            .image
    }
}

Making use of dot syntax extensions of CGContext like:

public extension CGContext {

    public static func rgb(size: CGSize) -> CGContext? {
        let sp = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
        let bi = CGImageAlphaInfo.PremultipliedLast.rawValue
        return CGBitmapContextCreate(nil, Int(ceil(size.width)), Int(ceil(size.height)), 8, 0, sp, bi)
    }

    public var image: CGImage? { return CGBitmapContextCreateImage(self) }

    public func draw(linearGradient
        gradient: CGGradient?,
        start: CGPoint,
        end: CGPoint,
        options: CGGradientDrawingOptions = [.DrawsBeforeStartLocation, .DrawsAfterEndLocation]
        ) -> CGContext
    {
        CGContextDrawLinearGradient(self, gradient, start, end, options)
        return self
    }
}

If you want to try this in a playground you can paste all this in, prefixed by the following snippet (for OS X) making sure you have the playground's timeline in the Assistant editor:

import XCPlayground
import Cocoa
import SpriteKit

let scene = SKScene(size: CGSize(width: 400, height: 400))
let view = SKView(frame: CGRect(origin: .zero, size: scene.size))
XCPlaygroundPage.currentPage.liveView = view
view.presentScene(scene)
Milos
  • 2,728
  • 22
  • 24
  • WOW!!!! YOU ARE PHENOMENAL!! I do however get an error for "return CGColorCreateGenericRGB(r, g, b, alpha)" saying it is unavailable. Any work arounds for this? I imported CoreGraphics like the apple documentation says but still nothing. – OriginalAlchemist Mar 29 '16 at 04:42
  • GOT IT! used this : return UIColor(red: r, green: g, blue: b, alpha: alpha).CGColor . THANK YOU SO SO SO MUCH! I REALLY APPRECIATE THIS! – OriginalAlchemist Mar 29 '16 at 05:03
  • Actually last question! Right now its going from AliceBlue & BurlyWood to AliceBlue and CornflowerBlue. What is the best way to create another set to go from and to different colors. For example After it goes from AliceBlue & BurlyWood to AliceBlue and CornflowerBlue, I want it to go from Red & Yellow to Green & Blue. Would i instantiate another HorizontalGradientNode? – OriginalAlchemist Mar 29 '16 at 05:18
  • You are very welcome! As for the questions: Note that this was a `Cocoa` example (for an OS X playground), but it seems you've figured that out. Let me know if you need any help converting this for iOS. As for the extra sets of colours to cycle through, I'd actually amend `HorizontalGradientNode` to take an `[[CGColor]]` or `[CGColor]...` instead (for an arbitrary number of colour sets). Would you like me to change the answer accordingly or you've got that? – Milos Mar 29 '16 at 07:50
  • Yes please would you mind changing the answer! Omg please! I'm new to all this, and you have helped so much! Thank you! I actually integrated it into my iOS application and it looks/works great! – OriginalAlchemist Mar 29 '16 at 09:04
  • You're awesome man, thank you so much! And if you don't mind and have some time please take a look at my other question here : http://stackoverflow.com/questions/36234960/skemiternode-with-avaudioplayer-for-music-visuals . You've helped so much already, so whatever help you could provide is enough for me. – OriginalAlchemist Mar 29 '16 at 11:11
1

In the comments following my initial answer @OriginalAlchemist's asked if the HorizontalGradientNode can be amended to cycle through more than two sets of colours. Here is one way to do it (assuming CGImage.withHorizontalGradient(size:colors:) from my first answer is available):

class HorizontalGradientNode : SKNode {

    init(size: CGSize, gradientColorSets: [[CGColor]], wait: NSTimeInterval, duration: NSTimeInterval) {
        super.init()

        var i = 0
        let setNextGradientTexture = SKAction.customActionWithDuration(0) { (node, _) in
            guard let sprite = node as? SKSpriteNode else { return }
            let colors = gradientColorSets[i]
            i = (i + 1) % gradientColorSets.count
            guard let image = CGImage.withHorizontalGradient(size: size, colors: colors) else { return }
            sprite.texture = SKTexture(CGImage: image)
            sprite.size = sprite.texture!.size()
        }

        let sprite1 = SKSpriteNode()
        let sprite2 = SKSpriteNode()

        sprite1.runAction(setNextGradientTexture)

        sprite2.alpha = 0
        sprite2.runAction(
            .repeatActionForever(
                .sequence(
                    [
                         setNextGradientTexture,
                        .waitForDuration(wait),
                        .fadeInWithDuration(duration),
                        .runBlock({ sprite1.runAction(setNextGradientTexture) }),
                        .waitForDuration(wait),
                        .fadeOutWithDuration(duration)
                    ]
                )
            )
        )

        addChild(sprite1)
        addChild(sprite2)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Usage:

// assuming
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)

let gradientColorSets: [[CGColor]] = [
    [.AliceBlue, .BurlyWood], // note that you can have more than two colours per set
    [.BurlyWood, .CornflowerBlue],
    [.CornflowerBlue, .ForestGreen],
    [.ForestGreen, .AliceBlue]
]

let bg = HorizontalGradientNode(size: scene.size, gradientColorSets: gradientColorSets, wait: 1, duration: 2)
scene.addChild(bg)

@OriginalAlchemist also asked for an iOS compatible version of the named CGColors:

public extension CGColor {

    public static func withHex(hex: Int, alpha: CGFloat = 1) -> CGColor {
        let x = max(0, min(hex, 0xffffff)) // allows force unwrapping of the return value
        let r = CGFloat((x & 0xff0000) >> 16)   / 0xff
        let g = CGFloat((x & 0x00ff00) >> 8)    / 0xff
        let b = CGFloat( x & 0x0000ff)          / 0xff
        let cp = CGColorSpaceCreateDeviceRGB()
        return CGColorCreate(cp, [r, g, b, alpha])!
    }

    public static var AliceBlue: CGColor { return .withHex(0xF0F8FF) }
    public static var BurlyWood: CGColor { return .withHex(0xDEB887) }
    public static var CornflowerBlue: CGColor { return .withHex(0x6495ED) }
    public static var ForestGreen: CGColor { return .withHex(0x228B22) }
    // see named colours at: http://www.w3schools.com/colors/colors_names.asp
}
Milos
  • 2,728
  • 22
  • 24
  • Wow, you're amazing man. Thank you so much. This helps a lot. Here is my email : nadalalyafaie@gmail.com . You can email me and stay connected, I'll remember this and if an opportunity presents itself, i know who to go to!! Also if and only if you have time and are feeling really generous, I asked another question here and would love some help : http://stackoverflow.com/questions/36234960/skemiternode-with-avaudioplayer-for-music-visuals . Thanks so much again! Wow! – OriginalAlchemist Mar 30 '16 at 00:11