I recently needed to implement the music visualizer in iOS swift as shown in the image/gif.
I used this TempiFFT to precesses the audio input and modified the UI for the required visual effects.
the main features of the visual effects are:
1) the gradient of the bars
2) the dashed effect in the bars
3) fading out the previous bars
for the fading effect i used this How To Make A Simple Drawing App with UIKit and Swift where it initially draws on temporary image and then copies to the main image with the reduced opacity (if required).
I somehow successfully implemented the features. The bars work properly but "didReceiveMemoryWarning" is called after sometime (around 3 minutes) and the app stops few seconds after that.
So how do I resolve the memory warning issue and the app stops issue
Following is the code for drawing bars:
func drawBars() {
// start drawng on temp image
UIGraphicsBeginImageContext(self.tempImage.frame.size)
// get context
let context = UIGraphicsGetCurrentContext()
tempImage.image?.draw(in: CGRect(x:0, y:0, width: tempImage.frame.width, height:tempImage.frame.height))
let height = Double(self.tempImage.frame.size.height)
let width = Double(self.tempImage.frame.size.width)
let barWidth = width / bars
//let barSpace = width / bars * 0.2
// number of bars
let count = self.fft?.numberOfBands ?? 0
// number of magnitudes
let fft = self.fft?.bandMagnitudes ?? [0, 0, 0]
if count == 0 {
return
}
// Draw the spectrum.
let maxDB: Float = 64.0
let minDB: Float = -32.0
let headroom = maxDB - minDB
//let colWidth = tempi_round_device_scale(d: CGFloat(width) / CGFloat(count))
// need to display only few (10) bars so will consider very (count/10)th bar
let divider = count / Int(bars) - 1
var barCount = 0
for i in 0..<count {
// checking if display bar or not
if i % divider != 0 || i / divider > 9 {
continue
}
let magnitude = fft[i]
// Incoming magnitudes are linear, making it impossible to see very low or very high values. Decibels to the rescue!
var magnitudeDB = TempiFFT.toDB(magnitude)
// Normalize the incoming magnitude so that -Inf = 0
magnitudeDB = max(0, magnitudeDB + abs(minDB))
let dbRatio = min(1.0, magnitudeDB / headroom)
let magnitudeNorm = CGFloat(dbRatio) * CGFloat(height)
//let colRect: CGRect = CGRect(x: x, y: plotYStart, width: colWidth, height: magnitudeNorm)
//let startPoint = CGPoint(x: viewWidth / 2, y: 0)
//let endPoint = CGPoint(x: viewWidth / 2, y: viewHeight)
//context.saveGState()
//context.clip(to: colRect)
//context.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: 0))
//context.restoreGState()
//x += colWidth
//}
//for var i in (0..<10) {
//print(barCount)
let x = Double(barCount) * barWidth + 0.5 * barWidth
// cliping the bar area for dash and gradient effect
context?.saveGState()
context?.addRect(CGRect(x:x - width / bars * 0.4, y:height - Double(magnitudeNorm), width:width / bars * 0.8, height:Double(magnitudeNorm)))
context?.clip()
// context?.drawLinearGradient(CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [UIColor.red.cgColor, UIColor.blue.cgColor] as CFArray, locations: [0, 1])! , start: CGPoint(x:x, y:height + 5), end: CGPoint(x:x, y:height - Double(magnitudeNorm)), options: CGGradientDrawingOptions(rawValue: 0))
// draw the gradient first
context?.drawLinearGradient(CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [UIColor.white.cgColor, UIColor.cyan.cgColor, UIColor.magenta.cgColor, UIColor.magenta.cgColor] as CFArray, locations: [0, 0.5, 0.85, 1])! , start: CGPoint(x:x, y:height + 5), end: CGPoint(x:x, y:0), options: CGGradientDrawingOptions(rawValue: 0))
context?.restoreGState()
//context?.addRect(CGRect(x:x - width / bars * 0.4, y:height - Double(magnitudeNorm), width:width / bars * 0.8, height:Double(magnitudeNorm)))
//context?.drawPath(using: CGPathDrawingMode.stroke)
// now draw the dash line
context?.move(to: CGPoint(x:x, y:height + 5))
context?.addLine(to: CGPoint(x:x, y:height - Double(magnitudeNorm)))
//context?.addLine(to: CGPoint(x:x, y:height - Double((i / divider + 1) * 10)))
context?.setLineCap(CGLineCap.butt)
context?.setLineWidth(CGFloat(width / bars * 0.85))
context?.setStrokeColor(red: 0, green: 0, blue: 0, alpha: 1)
context?.setBlendMode(CGBlendMode.normal)
context?.setLineDash(phase: 1, lengths: [5, 10])
context?.strokePath()
barCount += 1;
}
// save the image
tempImage.image = UIGraphicsGetImageFromCurrentImageContext()
tempImage.alpha = 1
UIGraphicsEndImageContext()
tempi_dispatch_main { () -> () in
//self.spectralView.fft = fft
//self.spectralView.setNeedsDisplay()
//merge the image with the previous image
//on main thread
self.refresh()
}
//refresh()
}
func refresh() {
UIGraphicsBeginImageContext(mainImage.frame.size)
//let context = UIGraphicsGetCurrentContext()
// draw the previous image with 0.9 opacity(transparancy) for a fade out effect
mainImage.image?.draw(in: CGRect(x:0, y:0, width:mainImage.frame.width, height:mainImage.frame.height), blendMode: CGBlendMode.normal, alpha: 0.9)
// draw the new bars over the previous one
tempImage.image?.draw(in: CGRect(x:0, y:0, width:mainImage.frame.width, height:mainImage.frame.height), blendMode: CGBlendMode.normal, alpha: 1)
// finally show that new image on the image view
mainImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImage.image = nil
}
Related
I'm trying to build a gradient progress bar with rounded corners in SpriteKit, but I'm completely stuck at this point. I've tried different combinations of SKCropNode, SKShapeNodes etc. but I can't seem to get it to work.
Any help is appreciated, kind regards!
It's about SKCropNode + its maskNode property. From the docs:
SKCropNode is a container node that you use to crop other nodes in the
scene. You add other nodes to a crop node and set the crop node's
maskNode property. For example, here are some ways you might specify a
mask:
An untextured sprite that limits content to a rectangular portion of
the scene.
A textured sprite that works as a precise per-pixel mask.
A collection of child nodes that form a unique shape.
You can animate the shape or contents of the mask to implement
interesting effects such as hiding or revealing.
So, a simple example would be like this:
class GameScene: SKScene {
override func sceneDidLoad() {
super.sceneDidLoad()
createProgressBar()
}
private func createProgressBar(){
let barFrame = CGRect(x: 0, y: 0, width: 300, height: 15)
if let cgImage = createImage(frame: barFrame) {
let texture = SKTexture(cgImage: cgImage)
let sprite = SKSpriteNode(texture: texture)
let cropNode = SKCropNode()
let mask = SKSpriteNode(color: .gray, size: barFrame.size)
cropNode.addChild(sprite)
cropNode.maskNode = mask
sprite.anchorPoint = CGPoint(x: 0.0, y: 0.5)
mask.anchorPoint = CGPoint(x: 0.0, y: 0.5)
var counter:Double = 0
let action = SKAction.run {[weak self, sprite] in
guard let `self` = self, counter < 100 else {
sprite?.removeAction(forKey: "loop")
return
}
counter += 1
let newWidth = self.getWidth(percents: counter, spriteWidth: barFrame.width)
print("Bar width \(newWidth), percentage \(counter)")
mask.size = CGSize(width: newWidth, height: barFrame.height)
}
let wait = SKAction.wait(forDuration: 0.05)
let sequence = SKAction.sequence([wait, action])
let loop = SKAction.repeatForever(sequence)
addChild(cropNode)
cropNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)
sprite.run(loop, withKey: "loop")
}
}
private func getWidth(percents:Double, spriteWidth:Double)->Double{
let onePercent = spriteWidth / 100.0
return onePercent * percents
}
private func createImage(frame barFrame:CGRect) -> CGImage?{
if let ciFilter = CIFilter(name: "CILinearGradient"){
let ciContext = CIContext()
ciFilter.setDefaults()
let startColor = CIColor(red: 0.75, green: 0.35, blue: 0.45, alpha: 1)
let endColor = CIColor(red: 0.45, green: 0.35, blue: 0.75, alpha: 1)
let startVector = CIVector(x: 0, y: 0)
let endVector = CIVector(x: barFrame.width, y: 0)
ciFilter.setValue(startColor, forKey: "inputColor0")
ciFilter.setValue(endColor, forKey: "inputColor1")
ciFilter.setValue(startVector, forKey: "inputPoint0")
ciFilter.setValue(endVector, forKey: "inputPoint1")
if let outputImage = ciFilter.outputImage {
let cgImage = ciContext.createCGImage(outputImage, from: CGRect(x: 0, y: 0, width: barFrame.width, height: barFrame.height))
return cgImage
}
}
return nil
}
}
Now cause this is just an example I won't go all the way to implement this right, but You can maybe make a class of it with designable and inspectable properties, optimize code, make it reusable etc. But the general idea is shown here.
You use SKCropNode to add progress bar in it, and use maskNode property to reveal progress bar as percentage increases. Also I gave a method to create texture programatically, but You can use just a .png file instead.
Crop node is here used only cause of a gradient (cause we don't wan't to scale image, but rather to show it part by part). Obviously, crop node is not needed if a progress bar had only one color.
Here is final result:
The code below should run fine: I didn't write it! :-)
I tried running some Swift Playground code by Simon J. Gladman (https://github.com/FlexMonkey) that corresponds to Chapter 8, "Warp Kernels," in his book, Core Image for Swift. As he wrote this code in Xcode 7.2 and I'm running it in Xcode 9.1, I had to update it in several places, which in most cases amounted to merely accepting the error handler's (or whatever it's called's) suggestions. After doing that, the following, more serious sounding message appeared:
error: Execution was interrupted, reason: signal SIGABRT.
The process has been left at the point where it was interrupted, use "thread >return -x" to return to the state before expression evaluation.
Other than knowing that SIGABRT stands for "signal abort," I have no idea what it means or, more importantly, how to trouble shoot the error. (I'm still just learning Swift and Cocoa/Cocoa Touch. . . .) Suggestions would be appreciated.
//: ## Barrel Distortion Warp Filter
import UIKit
import CoreImage
//: ### Warp Kernel
class CRTWarpFilter: CIFilter
{
var inputImage: CIImage?
var bend: CGFloat = 3.2
let crtWarpKernel = CIWarpKernel(source:
"kernel vec2 crtWarp(vec2 extent, float bend)" +
"{" +
" vec2 coord = ((destCoord() / extent) - 0.5) * 2.0;" +
" coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0);" +
" coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0);" +
" coord = ((coord / 2.0) + 0.5) * extent;" +
" return coord;" +
"}"
)
override var outputImage: CIImage!
{
if let inputImage = inputImage, let crtWarpKernel = crtWarpKernel
{
let arguments = [CIVector(x: inputImage.extent.size.width,
y: inputImage.extent.size.height),
bend] as [Any]
let extent = inputImage.extent
return crtWarpKernel.apply(extent: extent,
roiCallback:
{(index, rect) in return rect},
image: inputImage,
arguments: arguments)
}
return nil
}
}
let ciContext = CIContext()
func imageFromCIImage(source: CIImage) -> UIImage
{
let cgImage = ciContext.createCGImage(source, from: source.extent)
return UIImage(cgImage: cgImage!)
}
//: ### Swift Implementation of barrel warp kernel
//: `x` and `y` are pixel coordinates
let x = 65.0
let y = 55.0
//: `width` and `height` are extent
let width = 900.0
let height = 300.0
//: `crtWarpKernel` mechanics in Swift
var coordX = ((x / width) - 0.5) * 2.0
var coordY = ((y / height) - 0.5) * 2.0
coordX *= 1 + pow((abs(coordY) / 3.2), 2.0)
coordY *= 1 + pow((abs(coordX) / 3.2), 2.0)
coordX = ((coordX / 2.0) + 0.5) * width
coordY = ((coordY / 2.0) + 0.5) * height
// ----
let backgroundImage = CIFilter(name: "CICheckerboardGenerator",
withInputParameters: [
"inputColor0":
CIColor(red: 0.1, green: 0.1, blue: 0.1),
"inputColor1":
CIColor(red: 0.15, green: 0.15, blue: 0.15),
"inputCenter": CIVector(x: 0, y: 0),
"inputWidth": 50])!
.outputImage!.cropped(to: CGRect(x: 1, y: 1,
width: width - 2,
height: height - 2))
.composited(over: CIImage(color: CIColor(red: 0, green: 0, blue: 0)))
.cropped(to: CGRect(origin: CGPoint.zero,
size: CGSize(width: width, height: height)))
let blueBox = CIImage(color: CIColor(red: 0.5, green: 0.5, blue: 1, alpha: 0.7))
.cropped(to: CGRect(origin: CGPoint(x: coordX - 5, y: coordY - 5),
size: CGSize(width: 10, height: 10)))
let redBox = CIImage(color: CIColor(red: 1, green: 0, blue: 0, alpha: 0.7))
.cropped(to: CGRect(origin: CGPoint(x: x - 5, y: y - 5),
size: CGSize(width: 10, height: 10)))
let warpFilter = CRTWarpFilter()
warpFilter.inputImage = backgroundImage
let composite = CIFilter(name: "CIAdditionCompositing",
withInputParameters: [
kCIInputBackgroundImageKey: warpFilter.outputImage,
kCIInputImageKey: blueBox])!
.outputImage!
.applyingFilter("CIAdditionCompositing",
parameters: [kCIInputBackgroundImageKey: redBox])
let result = composite
The playground does not like that inputImage is not declared as #objc. If you change this:
class CRTWarpFilter: CIFilter
{
var inputImage: CIImage?
To this:
class CRTWarpFilter: CIFilter
{
#objc var inputImage: CIImage?
It's going to work fine.
Here's your code updated to Xcode 11 and Swift 5
//: ## Barrel Distortion Warp Filter
import UIKit
import CoreImage
//: ### Warp Kernel
class CRTWarpFilter: CIFilter
{
#objc var inputImage: CIImage?
var bend: CGFloat = 3.2
let crtWarpKernel = CIWarpKernel(source: """
kernel vec2 crtWarp(vec2 extent, float bend)
{
vec2 coord = ((destCoord() / extent) - 0.5) * 2.0;
coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0);
coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0);
coord = ((coord / 2.0) + 0.5) * extent;
return coord;
}
""")
override var outputImage: CIImage?
{
if let inputImage = inputImage, let crtWarpKernel = crtWarpKernel
{
let arguments = [CIVector(x: inputImage.extent.size.width,
y: inputImage.extent.size.height),
bend] as [Any]
let extent = inputImage.extent
return crtWarpKernel.apply(extent: extent,
roiCallback:
{(index, rect) in return rect},
image: inputImage,
arguments: arguments)
}
return nil
}
}
let ciContext = CIContext()
func imageFromCIImage(source: CIImage) -> UIImage
{
let cgImage = ciContext.createCGImage(source, from: source.extent)
return UIImage(cgImage: cgImage!)
}
//: ### Swift Implementation of barrel warp kernel
//: `x` and `y` are pixel coordinates
let x = 65.0
let y = 55.0
//: `width` and `height` are extent
let width = 900.0
let height = 300.0
//: `crtWarpKernel` mechanics in Swift
var coordX = ((x / width) - 0.5) * 2.0
var coordY = ((y / height) - 0.5) * 2.0
coordX *= 1 + pow((abs(coordY) / 3.2), 2.0)
coordY *= 1 + pow((abs(coordX) / 3.2), 2.0)
coordX = ((coordX / 2.0) + 0.5) * width
coordY = ((coordY / 2.0) + 0.5) * height
// ----
let backgroundImage = CIFilter(name: "CICheckerboardGenerator",
parameters: [
"inputColor0":
CIColor(red: 0.1, green: 0.1, blue: 0.1),
"inputColor1":
CIColor(red: 0.15, green: 0.15, blue: 0.15),
"inputCenter": CIVector(x: 0, y: 0),
"inputWidth": 50])!
.outputImage!.cropped(to: CGRect(x: 1, y: 1,
width: width - 2,
height: height - 2))
.composited(over: CIImage(color: CIColor(red: 0, green: 0, blue: 0)))
.cropped(to: CGRect(origin: CGPoint.zero,
size: CGSize(width: width, height: height)))
let blueBox = CIImage(color: CIColor(red: 0.5, green: 0.5, blue: 1, alpha: 0.7))
.cropped(to: CGRect(origin: CGPoint(x: coordX - 5, y: coordY - 5),
size: CGSize(width: 10, height: 10)))
let redBox = CIImage(color: CIColor(red: 1, green: 0, blue: 0, alpha: 0.7))
.cropped(to: CGRect(origin: CGPoint(x: x - 5, y: y - 5),
size: CGSize(width: 10, height: 10)))
let warpFilter = CRTWarpFilter()
warpFilter.inputImage = backgroundImage
let composite = CIFilter(name: "CIAdditionCompositing",
parameters: [
kCIInputBackgroundImageKey: warpFilter.outputImage,
kCIInputImageKey: blueBox])!
.outputImage!
.applyingFilter("CIAdditionCompositing",
parameters: [kCIInputBackgroundImageKey: redBox])
let result = composite
I am wondering how I would go about implementing a wave-like border of a UIView. Is this possible through UIView's alone? Or would creating this appearance through a UIImageView be the way to go?
An example might be something similar to:
Thanks for your help!
Here is a code based solution that doesn't require any images. This creates a custom view using UIBezierPath to create the sine waves.
import UIKit
class WavyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
return nil // TODO
}
override func draw(_ rect: CGRect) {
// Fill the whole background with the darkest blue color
UIColor(red: 0.329, green: 0.718, blue: 0.875, alpha: 1).set()
let bg = UIBezierPath(rect: rect)
bg.fill()
// Add the first sine wave filled with a very transparent white
let top1: CGFloat = 17.0
let wave1 = wavyPath(rect: CGRect(x: 0, y: top1, width: frame.width, height: frame.height - top1), periods: 1.5, amplitude: 21, start: 0.55)
UIColor(white: 1.0, alpha: 0.1).set()
wave1.fill()
// Add the second sine wave over the first
let top2: CGFloat = 34.0
let wave2 = wavyPath(rect: CGRect(x: 0, y: top2, width: frame.width, height: frame.height - top2), periods: 1.5, amplitude: 21, start: 0.9)
UIColor(white: 1.0, alpha: 0.15).set()
wave2.fill()
// Add the text
let paraAttrs = NSMutableParagraphStyle()
paraAttrs.alignment = .center
let textRect = CGRect(x: 0, y: frame.maxY - 64, width: frame.width, height: 24)
let textAttrs = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 20), NSForegroundColorAttributeName: UIColor(white: 1.0, alpha: 0.9), NSParagraphStyleAttributeName: paraAttrs]
("New user? Register here." as NSString).draw(in: textRect, withAttributes: textAttrs)
}
// This creates the desired sine wave bezier path
// rect is the area to fill with the sine wave
// periods is how may sine waves fit across the width of the frame
// amplitude is the height in points of the sine wave
// start is an offset in wavelengths for the left side of the sine wave
func wavyPath(rect: CGRect, periods: Double, amplitude: Double, start: Double) -> UIBezierPath {
let path = UIBezierPath()
// start in the bottom left corner
path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
let radsPerPoint = Double(rect.width) / periods / 2.0 / Double.pi
let radOffset = start * 2 * Double.pi
let xOffset = Double(rect.minX)
let yOffset = Double(rect.minY) + amplitude
// This loops through the width of the frame and calculates and draws each point along the size wave
// Adjust the "by" value as needed. A smaller value gives smoother curve but takes longer to draw. A larger value is quicker but gives a rougher curve.
for x in stride(from: 0, to: Double(rect.width), by: 6) {
let rad = Double(x) / radsPerPoint + radOffset
let y = sin(rad) * amplitude
path.addLine(to: CGPoint(x: x + xOffset, y: y + yOffset))
}
// Add the last point on the sine wave at the right edge
let rad = Double(rect.width) / radsPerPoint + radOffset
let y = sin(rad) * amplitude
path.addLine(to: CGPoint(x: Double(rect.maxX), y: y + yOffset))
// Add line from the end of the sine wave to the bottom right corner
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
// Close the path
path.close()
return path
}
}
// This creates the view with the same size as the image posted in the question
let wavy = WavyView(frame: CGRect(x: 0, y: 0, width: 502, height: 172))
The result of running this code in a Swift playground gives the following:
Obviously you can adjust any of the values in the code above to tweak the result.
The easiest approach would be to use a UIImageView. However, it is also possible by creating a custom border for the UIView but that will require a lot of code to draw the shapes.
I am trying to rotate a CAShapeLayer with respect to a particular anchor point. But when i apply
firstLayer.transform = CATransform3DMakeRotation(CGFloat(M_2_PI), 0, 0, 0)
nothing happens.
i am making a custom UIButton , in which i am adding a layer
import UIKit
#IBDesignable
class CustomButtonTwo: UIButton {
var context = UIGraphicsGetCurrentContext()
#IBInspectable var Thickness : CGFloat = 2
let firstLayer = CAShapeLayer()
var width = CGFloat()
var height = CGFloat()
override func awakeFromNib() {
super.awakeFromNib()
width = self.frame.width
height = self.frame.height
print("\(width) : \(height)")
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let afirstStartPoint = CGPointMake(width * 0.1, (height - 3 * Thickness) / 6)
let bfirstStartPoint = CGPointMake(width * 0.1, (height - 3 * Thickness) / 6 + Thickness)
let afirstMiddlePoint = CGPointMake(width * 0.5, (height - 3 * Thickness) / 6 )
let bfirstMiddlePoint = CGPointMake(width * 0.5, (height - 3 * Thickness) / 6 + Thickness)
print(afirstMiddlePoint)
print(afirstStartPoint)
print(bfirstMiddlePoint)
print(bfirstStartPoint)
let firstPath = UIBezierPath()
firstPath.moveToPoint(afirstStartPoint)
firstPath.addLineToPoint(afirstMiddlePoint)
firstPath.addLineToPoint(bfirstMiddlePoint)
firstPath.addLineToPoint(bfirstStartPoint)
firstPath.addLineToPoint(afirstStartPoint)
firstPath.closePath()
firstLayer.frame = self.frame
UIColor.greenColor().setFill()
firstPath.fill()
firstLayer.path = firstPath.CGPath
firstLayer.anchorPoint = afirstStartPoint
firstLayer.transform = CATransform3DMakeRotation(CGFloat(M_2_PI), 0, 0, 1)
layer.addSublayer(firstLayer)
}
}
i want to make a line with a particular thickness and rotate it along a particular point (while animating).
any help appreciated!
simulator Screenshot
Rotating by 2_PI (360) will mean layer will end up back at its original place (0 rotation). Try PI_2 (90) or PI (180) or another angle. Also you need to specify the axis of rotation.
This call rotates by 90 degrees around z-axis:
CATransform3DMakeRotation(CGFloat(M_PI_2), 0, 0, 1.0)
I want to draw a grid which would fill the whole screen of an iOS device with squares which form a gradient and this is the method I rote:
static func getGradientImage(fromHue first_hue: CGFloat, toHue second_hue: CGFloat) -> UIImage{
UIGraphicsBeginImageContextWithOptions(CGSize(width: SystemDefaults.screen.width, height: SystemDefaults.screen.height), false, 0)
let context = UIGraphicsGetCurrentContext()
let pixel: CGFloat = SystemDefaults.screen.height / 16
let raw_cols: CGFloat = SystemDefaults.screen.width / pixel
let cols = Int(raw_cols + 0.4) //Because for iPhone 4 this number is 10.666 and converting it to Int gives 10, which is not enough. It does not affect other screens.
let width: CGFloat = SystemDefaults.screen.width / CGFloat(cols)
for index in 0..<16{
let mlt_row = CGFloat(16 - index)
for idx in 0..<cols{
let mlt_col = CGFloat((idx + 5) * (16 / cols))
let rct = CGRect(x: CGFloat(idx) * width, y: CGFloat(index) * pixel, width: width, height: pixel) //That's where I think the problem is. Coordinates should be more even
let hue = (first_hue * mlt_row + second_hue * mlt_col) / (mlt_row + mlt_col)
let clr = UIColor(hue: hue, saturation: 0.85, brightness: 1, alpha: 1)
CGContextSetFillColorWithColor(context, clr.CGColor)
CGContextFillRect(context, rct)
}
}
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
The problem is that an output Image has tiny lines between columns and I assume It's because the width parameter is a complicated CGFloat and system fails to "move" the next rct for exactly that distance. And that's because, for example, iPhone 5's screen isn't exactly 16/9, rather 16/9.(something). How should I draw the Image so that I fill screen with exactly 144 (for iPhone5) squares with no lines in between them?