Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatic "fuzzy" style background for UIView

Of course, it's trivial to set a plain color for a background:

enter image description here

These days, instead of using "plain gray", it is popular to use a "fuzzy" or "cloudy" background, as a design feature in apps.

For example, here's a couple "fuzzy" backgrounds - it's just a plain color with perhaps some noise and maybe blur on that.

You can see backgrounds something like this all over, consider popular feed apps (whassapp etc). It's a "fad" of our day.

enter image description here

enter image description here

It occurred to me, it would be fantastic if you could do this in code in Swift

Note: starting with a PNG is not an elegant solution:

Hopefully it is possible to generate everything programmatically from scratch.

It would be great if the Inspector had a slider in the IBDesignable style, "Add faddish 'grainy' background..." - Should be possible in the new era!

like image 560
Fattie Avatar asked Aug 14 '15 13:08

Fattie


2 Answers

in Swift 3

import UIKit

let noiseImageCache = NSCache<AnyObject, AnyObject>()

@IBDesignable class NoiseView: UIView {

    let noiseImageSize = CGSize(width: 128.0, height: 128.0)

    @IBInspectable var noiseColor: UIColor = UIColor.black {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMinAlpha: CGFloat = 0 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMaxAlpha: CGFloat = 0.5 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noisePasses: Int = 3 {
        didSet {
            noisePasses = max(0, noisePasses)
            setNeedsDisplay()
        }
    }
    @IBInspectable var noiseSpacing: Int = 1 {
        didSet {
            noiseSpacing = max(1, noiseSpacing)
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        UIColor(patternImage: currentUIImage()).set()
        UIRectFillUsingBlendMode(bounds, .normal)
    }

    private func currentUIImage() -> UIImage {

        //  Key based on all parameters
        let cacheKey = "\(noiseImageSize),\(noiseColor),\(noiseMinAlpha),\(noiseMaxAlpha),\(noisePasses)"
        var image = noiseImageCache.object(forKey: cacheKey as AnyObject) as? UIImage

        if image == nil {
            image = generatedUIImage()

            #if !TARGET_INTERFACE_BUILDER
                noiseImageCache.setObject(image!, forKey: cacheKey as AnyObject)
            #endif
        }
        return image!
    }

    private func generatedUIImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(noiseImageSize, false, 0)
        let accuracy: CGFloat = 1000.0
        for _ in 0..<noisePasses {
            for y in 0..<Int(noiseImageSize.height) {
                for x in 0..<Int(noiseImageSize.width) {
                    if Int(arc4random()) % noiseSpacing == 0 {
                        let alpha = (CGFloat(arc4random() % UInt32((noiseMaxAlpha - noiseMinAlpha) * accuracy)) / accuracy) + noiseMinAlpha
                        noiseColor.withAlphaComponent(alpha).set()
                        UIRectFill(CGRect(x: x, y: y, width: 1, height: 1))
                    }
                }
            }
        }
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image!
    }
}
like image 96
Wan Mook Kang Avatar answered Oct 12 '22 11:10

Wan Mook Kang


This will get you started, based on something I wrote a long time ago:

enter image description here

@IBInspectable properties:

  • noiseColor: the noise/grain color, this is applied over the view's backgroundColor
  • noiseMinAlpha: the minimum alpha the randomized noise can be
  • noiseMaxAlpha: the maximum alpha the randomized noise can be
  • noisePasses: how many times to apply the noise, more passes will be slower but can result in a better noise effect
  • noiseSpacing: how common the randomized noise occurs, higher spacing means the noise will be less frequent

Explanation:

When any of the designable noise properties change the view is flagged for redraw. In the draw function the UIImage is generated (or pulled from NSCache if available).

In the generation method each pixel is iterated over and if the pixel should be noise (depending on the spacing parameter), the noise color is applied with a randomized alpha channel. This is done as many times as the number of passes.

.

// NoiseView.swift
import UIKit

let noiseImageCache = NSCache()

@IBDesignable class NoiseView: UIView {

    let noiseImageSize = CGSizeMake(128, 128)

    @IBInspectable var noiseColor: UIColor = UIColor.blackColor() {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMinAlpha: CGFloat = 0 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noiseMaxAlpha: CGFloat = 1 {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var noisePasses: Int = 1 {
        didSet {
            noisePasses = max(0, noisePasses)
            setNeedsDisplay()
        }
    }
    @IBInspectable var noiseSpacing: Int = 1 {
        didSet {
            noiseSpacing = max(1, noiseSpacing)
            setNeedsDisplay()
        }
    }

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)

        UIColor(patternImage: currentUIImage()).set()
        UIRectFillUsingBlendMode(bounds, .Normal)
    }

    private func currentUIImage() -> UIImage {

        //  Key based on all parameters
        let cacheKey = "\(noiseImageSize),\(noiseColor),\(noiseMinAlpha),\(noiseMaxAlpha),\(noisePasses)"

        var image = noiseImageCache.objectForKey(cacheKey) as! UIImage!

        if image == nil {
            image = generatedUIImage()

            #if !TARGET_INTERFACE_BUILDER
                noiseImageCache.setObject(image, forKey: cacheKey)
            #endif
        }

        return image
    }

    private func generatedUIImage() -> UIImage {

        UIGraphicsBeginImageContextWithOptions(noiseImageSize, false, 0)

        let accuracy: CGFloat = 1000.0

        for _ in 0..<noisePasses {
            for y in 0..<Int(noiseImageSize.height) {
                for x in 0..<Int(noiseImageSize.width) {
                    if random() % noiseSpacing == 0 {
                        let alpha = (CGFloat(random() % Int((noiseMaxAlpha - noiseMinAlpha) * accuracy)) / accuracy) + noiseMinAlpha
                        noiseColor.colorWithAlphaComponent(alpha).set()
                        UIRectFill(CGRectMake(CGFloat(x), CGFloat(y), 1, 1))
                    }
                }
            }
        }

        let image = UIGraphicsGetImageFromCurrentImageContext() as UIImage

        UIGraphicsEndImageContext()

        return image
    }
}
like image 22
SomeGuy Avatar answered Oct 12 '22 11:10

SomeGuy