Related
Using iOS 10.20 & Swift 3.0
Want to use this excellent piece of code written by Stephen Poletto a while back in my code, but need it in Swift 3.0 really.
https://github.com/spoletto/SPUserResizableView
Spent three hours on it today plodding thru, no confidence it will work, but I have to try, must be easier then reinventing the wheel no; anyway got stuck on a few constructs I hope I can find some help on in SO.
I need to translate this ...
- (void)resizeUsingTouchLocation:(CGPoint)touchPoint {
// (1) Update the touch point if we're outside the superview.
if (self.preventsPositionOutsideSuperview) {
CGFloat border = kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2;
if (touchPoint.x < border) {
touchPoint.x = border;
}
I got it in Swift, but I fear unlike objective C it seems you cannot change the value of a parameter as he seems to be doing here?
I got ..
func resizeUsingTouchLocation(touchPoint: CGPoint) {
// (1) Update the touch point if we're outside the superview.
if (self.preventsPositionOutsideSuperview) {
let border:CGFloat = CGFloat(kSPUserResizableViewGlobalInset) + CGFloat(kSPUserResizableViewInteractiveBorderSize) / 2.0;
if (touchPoint.x < border) {
touchPoint.x = border
}
It generates an error, cannot change a let property, touchPoint in this case! Got a few more, but these two in particular are phasing me ...
In Swift, you can not change the input parameters. Before Swift 3, you could add a var before the parameter name and then you'd have a copy that you can modify. The Swift 3 way is to add var variableName = variableName at the top of your function:
func resizeUsingTouchLocation(touchPoint: CGPoint) {
var touchPoint = touchPoint
// (1) Update the touch point if we're outside the superview.
if self.preventsPositionOutsideSuperview {
let border = CGFloat(kSPUserResizableViewGlobalInset) + CGFloat(kSPUserResizableViewInteractiveBorderSize) / 2.0
if touchPoint.x < border {
touchPoint.x = border
}
I also removed unneeded type declarations, () in if statements, and a ;.
How do you create the parallax focus effect on a collection view cell with a custom view? If I were using an image view the property to set would be adjustsImageWhenAncestorFocused but my collection view cell contains a subclassed UIView with custom content drawn using core graphics.
The answer by #raulriera is nice, but only shifts the cell around in 2D.
Also, the OP asked for an objective-C example.
I was also looking to do this effect for the exact same reason - I had UICollectionView with cells containing images and labels.
I created a UIMotionEffectGroup subclass, since getting near to the Apple TV effect seems to require four different motion effects. The first two are the flat movements as in #raulriera, and the other two are the 3D rotations.
Just the shiny environment layer to go now. Any takers? :-)
Here is my code for the motion effect group:
(The shiftDistance and tiltAngle constants set the magnitude of the effect. The given values look pretty similar to the Apple TV effect.)
#import <UIKit/UIKit.h>
#import "UIAppleTvMotionEffectGroup.h"
#implementation UIAppleTvMotionEffectGroup
- (id)init
{
if ((self = [super init]) != nil)
{
// Size of shift movements
CGFloat const shiftDistance = 10.0f;
// Make horizontal movements shift the centre left and right
UIInterpolatingMotionEffect *xShift = [[UIInterpolatingMotionEffect alloc]
initWithKeyPath:#"center.x"
type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
xShift.minimumRelativeValue = [NSNumber numberWithFloat: shiftDistance * -1.0f];
xShift.maximumRelativeValue = [NSNumber numberWithFloat: shiftDistance];
// Make vertical movements shift the centre up and down
UIInterpolatingMotionEffect *yShift = [[UIInterpolatingMotionEffect alloc]
initWithKeyPath:#"center.y"
type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
yShift.minimumRelativeValue = [NSNumber numberWithFloat: shiftDistance * -1.0f];
yShift.maximumRelativeValue = [NSNumber numberWithFloat: shiftDistance];
// Size of tilt movements
CGFloat const tiltAngle = M_PI_4 * 0.125;
// Now make horizontal movements effect a rotation about the Y axis for side-to-side rotation.
UIInterpolatingMotionEffect *xTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
// CATransform3D value for minimumRelativeValue
CATransform3D transMinimumTiltAboutY = CATransform3DIdentity;
transMinimumTiltAboutY.m34 = 1.0 / 500;
transMinimumTiltAboutY = CATransform3DRotate(transMinimumTiltAboutY, tiltAngle * -1.0, 0, 1, 0);
// CATransform3D value for maximumRelativeValue
CATransform3D transMaximumTiltAboutY = CATransform3DIdentity;
transMaximumTiltAboutY.m34 = 1.0 / 500;
transMaximumTiltAboutY = CATransform3DRotate(transMaximumTiltAboutY, tiltAngle, 0, 1, 0);
// Set the transform property boundaries for the interpolation
xTilt.minimumRelativeValue = [NSValue valueWithCATransform3D: transMinimumTiltAboutY];
xTilt.maximumRelativeValue = [NSValue valueWithCATransform3D: transMaximumTiltAboutY];
// Now make vertical movements effect a rotation about the X axis for up and down rotation.
UIInterpolatingMotionEffect *yTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
// CATransform3D value for minimumRelativeValue
CATransform3D transMinimumTiltAboutX = CATransform3DIdentity;
transMinimumTiltAboutX.m34 = 1.0 / 500;
transMinimumTiltAboutX = CATransform3DRotate(transMinimumTiltAboutX, tiltAngle * -1.0, 1, 0, 0);
// CATransform3D value for maximumRelativeValue
CATransform3D transMaximumTiltAboutX = CATransform3DIdentity;
transMaximumTiltAboutX.m34 = 1.0 / 500;
transMaximumTiltAboutX = CATransform3DRotate(transMaximumTiltAboutX, tiltAngle, 1, 0, 0);
// Set the transform property boundaries for the interpolation
yTilt.minimumRelativeValue = [NSValue valueWithCATransform3D: transMinimumTiltAboutX];
yTilt.maximumRelativeValue = [NSValue valueWithCATransform3D: transMaximumTiltAboutX];
// Add all of the motion effects to this group
self.motionEffects = #[xShift, yShift, xTilt, yTilt];
[xShift release];
[yShift release];
[xTilt release];
[yTilt release];
}
return self;
}
#end
I used it like this in my custom UICollectionViewCell subclass:
#implementation MyCollectionViewCell
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
// Create a static instance of the motion effect group (could do this anywhere, really, maybe init would be better - we only need one of them.)
static UIAppleTVMotionEffectGroup *s_atvMotionEffect = nil;
if (s_atvMotionEffect == nil)
{
s_atvMotionEffect = [[UIAppleTVMotionEffectGroup alloc] init];
}
[coordinator addCoordinatedAnimations: ^{
if (self.focused)
{
[self addMotionEffect: s_atvMotionEffect];
}
else
{
[self removeMotionEffect: s_atvMotionEffect];
}
completion: ^{
}];
}
#end
All you need to do is add a UIMotionEffect to your subviews. Something like this
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({ [unowned self] in
if self.focused {
let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .TiltAlongVerticalAxis)
verticalMotionEffect.minimumRelativeValue = -10
verticalMotionEffect.maximumRelativeValue = 10
let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .TiltAlongHorizontalAxis)
horizontalMotionEffect.minimumRelativeValue = -10
horizontalMotionEffect.maximumRelativeValue = 10
let motionEffectGroup = UIMotionEffectGroup()
motionEffectGroup.motionEffects = [horizontalMotionEffect, verticalMotionEffect]
yourView.addMotionEffect(motionEffectGroup)
}
else {
// Remove the effect here
}
}, completion: nil)
}
I've converted Simon Tillson's answer to swift 3.0 and posted here to save typing for people in the future. Thanks very much for a great solution.
class UIAppleTVMotionEffectGroup : UIMotionEffectGroup{
// size of shift movements
let shiftDistance : CGFloat = 10.0
let tiltAngle : CGFloat = CGFloat(M_PI_4) * 0.125
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init() {
super.init()
// Make horizontal movements shift the centre left and right
let xShift = UIInterpolatingMotionEffect(keyPath: "center.x", type: UIInterpolatingMotionEffectType.tiltAlongHorizontalAxis)
xShift.minimumRelativeValue = shiftDistance * -1.0
xShift.maximumRelativeValue = shiftDistance
let yShift = UIInterpolatingMotionEffect(keyPath: "center.y", type: UIInterpolatingMotionEffectType.tiltAlongVerticalAxis)
yShift.minimumRelativeValue = 0.0-shiftDistance
yShift.maximumRelativeValue = shiftDistance
let xTilt = UIInterpolatingMotionEffect(keyPath: "layer.transform", type: UIInterpolatingMotionEffectType.tiltAlongHorizontalAxis)
var transMinimumTiltAboutY = CATransform3DIdentity
transMinimumTiltAboutY.m34 = 1.0 / 500.0
transMinimumTiltAboutY = CATransform3DRotate(transMinimumTiltAboutY, tiltAngle * -1.0, 0, 1, 0)
var transMaximumTiltAboutY = CATransform3DIdentity
transMaximumTiltAboutY.m34 = 1.0 / 500.0
transMaximumTiltAboutY = CATransform3DRotate(transMaximumTiltAboutY, tiltAngle , 0, 1, 0)
xTilt.minimumRelativeValue = transMinimumTiltAboutY
xTilt.maximumRelativeValue = transMaximumTiltAboutY
let yTilt = UIInterpolatingMotionEffect(keyPath: "layer.transform", type: UIInterpolatingMotionEffectType.tiltAlongVerticalAxis)
var transMinimumTiltAboutX = CATransform3DIdentity
transMinimumTiltAboutX.m34 = 1.0 / 500.0
transMinimumTiltAboutX = CATransform3DRotate(transMinimumTiltAboutX, tiltAngle * -1.0, 1, 0, 0)
var transMaximumTiltAboutX = CATransform3DIdentity
transMaximumTiltAboutX.m34 = 1.0 / 500.0
transMaximumTiltAboutX = CATransform3DRotate(transMaximumTiltAboutX, tiltAngle , 1, 0, 0)
yTilt.minimumRelativeValue = transMinimumTiltAboutX
yTilt.maximumRelativeValue = transMaximumTiltAboutX
self.motionEffects = [xShift,yShift,xTilt,yTilt]
}
}
I have added a little pop to the part in the UICollectionView subclass. Note the struct wrapper for the static variable
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
struct wrapper {
static let s_atvMotionEffect = UIAppleTVMotionEffectGroup()
}
coordinator.addCoordinatedAnimations( {
var scale : CGFloat = 0.0
if self.isFocused {
self.addMotionEffect(wrapper.s_atvMotionEffect)
scale = 1.2
} else {
self.removeMotionEffect(wrapper.s_atvMotionEffect)
scale = 1.0
}
let transform = CGAffineTransform(scaleX: scale, y: scale)
self.layer.setAffineTransform(transform)
},completion: nil)
}
I'm learning Swift. As a test, I'm translating some of my old Objective-C programs to swift. But I have a crazy error: In Objective-C I have the following code:
- (CGSize)makeSizeFromCentimetersWidth: (CGFloat)width andY: (CGFloat)height {
NSScreen *screen = [NSScreen mainScreen];
NSDictionary *description = [screen deviceDescription];
NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:#"NSScreenNumber"] unsignedIntValue]);
CGFloat resolution = (displayPixelSize.width / displayPhysicalSize.width) * 25.4f;
CGFloat pixelsWidth = 0.394 * width * resolution;
CGFloat pixelsHeight = 0.394 * height * resolution;
return CGSizeMake(pixelsWidth, pixelsHeight);
}
In swift I have translated to this:
func makeSizeFromCentimeters(width: CGFloat, height: CGFloat) -> CGSize {
var screen: NSScreen = NSScreen.mainScreen()!
var description: NSDictionary = screen.deviceDescription
var displayPixelSize: NSSize = description.objectForKey(NSDeviceSize)!.sizeValue
var displayPhysicalSize: CGSize = CGDisplayScreenSize(description.objectForKey("NSScreenNumber")!.unsignedIntValue)
var resolution = (displayPixelSize.width / displayPhysicalSize.width) * 25.4
var pixelsWidth: CGFloat = 0.394 * width * resolution
var pixelsHeight: CGFloat = 0.394 * height * resolution
return CGSizeMake(pixelsWidth, pixelsHeight)
}
In Objective-C the code does what it should: Calculate a size from centimeters to pixels, to give out (in my case) an NSImageView with exactly the size of the given centimeters. But in Swift, the returned size, is always 0:
NSLog("%f", makeSizeFromCentimeters(2, height: 2).width)
NSLog("%f", makeSizeFromCentimeters(2, height: 2).height)
Is there an translating error? Which variable is 0? (No idea why it should be 0 if it's not caused by a variable).
Thank you for your help!
So I've been moving from a legacy profile to Core Profile for the last couple of days. I'd already moved much of my functionality to use VBOs and shaders, so I thought it wouldn't take that much work.
However, I can't get my new core profile contexts to draw anything at all using glDrawElements. My application manipulates textures in an app wide background openglContext, the GUI shows various stages of that using OpenGL views that share contexts with the background context.
Each texture object builds it's own VBOs for texture coords and colours as required, leaving me only to provide new vertex VBOs for displaying in views. The number of vertices and their drawing is standardised so I can share Index buffers.
This is my Pixel format shared between all contexts:
+ (NSOpenGLPixelFormat *) defaultPixelFormat
{
NSOpenGLPixelFormatAttribute attrs[] =
{
kCGLPFAOpenGLProfile, kCGLOGLPVersion_3_2_Core,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFABackingStore,
NSOpenGLPFAAllowOfflineRenderers,
NSOpenGLPFAStencilSize, 8,
NSOpenGLPFAColorSize, 32,
NSOpenGLPFADepthSize, 24,
0
};
NSOpenGLPixelFormat* pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
return pixFmt;
}
This is a short example of how I setup my VAOs without using a texture, I just want to draw something!
- (void) genTestVao
{
// Generate buffers first to simulate app environment
GLfloat verts[] = {
0.0, 0.0, 0.0, 0.0,
100.0, 0.0, 0.0, 0.0,
0.0, 100.0, 0.0, 0.0,
100.0, 100.0, 0.0, 0.0
};
GLfloat colors[] = {
1.0, 1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 0.0
};
GLushort indices[] = {0, 1, 2, 3};
if (_testVBuffer) {
glDeleteBuffers(1, &_testVBuffer);
}
if (_testCBuffer) {
glDeleteBuffers(1, &_testCBuffer);
}
if (_testIBuffer) {
glDeleteBuffers(1, &_testIBuffer);
}
glGenBuffers(1, &_testVBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _testVBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW);
glGenBuffers(1, &_testCBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _testCBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// vert and colors buffers done
glGenBuffers(1, &_testIBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _testIBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// Index buffer done
// Generate VAO with pre stored buffers
if (_testVAO) {
glDeleteVertexArrays(1, &_testVAO);
}
glGenVertexArrays(1, &_testVAO);
glBindVertexArray(_testVAO);
// Vertex
glBindBuffer(GL_ARRAY_BUFFER, _testVBuffer);
glEnableVertexAttribArray(kSCGLVertexAttribPosition);
glVertexAttribPointer(kSCGLVertexAttribPosition, 4, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 4, 0);
// Colors
glBindBuffer(GL_ARRAY_BUFFER, _testCBuffer);
glEnableVertexAttribArray(kSCGLColorAttribPosition);
glVertexAttribPointer(kSCGLColorAttribPosition, 4, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 4, 0);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
Setting up a view matrix:
_mvpMatrix = GLKMatrix4Multiply(
GLKMatrix4MakeOrtho(0.0, self.bounds.size.width, 0.0, self.bounds.size.height, -1.0, 1.0),
GLKMatrix4Identity);
The drawing code:
glUseProgram(self.testShader.shaderProgram);
glUniformMatrix4fv(self.testShader.mvpMatrixLocation, 1, GL_FALSE, self.mvpMatrix.m);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _testIBuffer);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_RECTANGLE, 0);
glUseProgram(0);
The vertex Shader:
#version 150
in vec4 position;
in vec4 color;
uniform mat4 mvpMatrix;
out vec4 vertex_color;
void main()
{
// perform standard transform on vertex
gl_Position = position * mvpMatrix;
vertex_color = color;
}
The Fragment Shader:
#version 150
in vec4 vertex_color;
out vec4 colourout;
void main()
{
colourout = vertex_color;
}
And finaly the code that links shader and vertices and binds the attribute locations:
- (BOOL) createProgramObjectWithVertexShader:(GLuint) vertShader withFragShader:(GLuint) fragShader
{
_shaderProgram = glCreateProgram();
glAttachShader(_shaderProgram, vertShader);
glBindAttribLocation(_shaderProgram, kSCGLVertexAttribPosition, "position");
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
NSLog(#"Error generated getting position!");
if (error == GL_INVALID_VALUE) {
NSLog(#"Invalid value");
} else if (error == GL_INVALID_OPERATION) {
NSLog(#"Invalid operation");
} else {
NSLog(#"unexpected error");
}
}
glBindAttribLocation(_shaderProgram, kSCGLColorAttribPosition, "color");
error = glGetError();
if (error != GL_NO_ERROR) {
NSLog(#"Error generated getting color!");
if (error == GL_INVALID_VALUE) {
NSLog(#"Invalid value");
} else if (error == GL_INVALID_OPERATION) {
NSLog(#"Invalid operation");
} else {
NSLog(#"unexpected error");
}
}
//glBindAttribLocation(_shaderProgram, kSCGLNormalAttribPosition, "normal");
//glBindAttribLocation(_shaderProgram, kSCGLTexCoordPosition, "texcoord");
error = glGetError();
if (error != GL_NO_ERROR) {
NSLog(#"Error generated getting texcoord!");
if (error == GL_INVALID_VALUE) {
NSLog(#"Invalid value");
} else if (error == GL_INVALID_OPERATION) {
NSLog(#"Invalid operation");
} else {
NSLog(#"unexpected error");
}
}
glAttachShader(_shaderProgram, fragShader);
glLinkProgram(_shaderProgram);
glDeleteShader(vertShader);
glDeleteShader(fragShader);
GLint result = GL_FALSE;
GLint infoLogLength = 0;
glGetProgramiv(_shaderProgram, GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
char errMsg[infoLogLength];
glGetProgramInfoLog(_shaderProgram, infoLogLength, &infoLogLength, errMsg);
NSString *msg = [NSString stringWithUTF8String:errMsg];
NSLog(#"Self = %#", self);
NSLog(#"Validate program failed with %#", msg);
if (![msg hasPrefix:#"WARNING:"]) {
NSLog(#"Fatal");
glDeleteProgram(_shaderProgram);
return NO;
}
}
if (![self getUniformLocations]) {
NSLog(#"Failed getting uniform variables for %#", self.shaderName);
glDeleteProgram(_shaderProgram);
return NO;
}
return YES;
}
I'm sure it's something simple, but I just can't see what it is and it's driving me crazy. The opengl view is setup correctly, if I clear it with colours, it shows correctly, it just won't draw my elements....
Part of the reason I'm moving to core profile is to share code with an iOS app, except for some simple changes, most of my opengl code is es compatible.
EDIT 1:
I created a rough and ready XCode project that shows the basics on GitHub. The app delegate holds a base shared openglContext and loads the test shader. The openGLView is based on a shared context from the App delegate:
EDIT 2:
I updated the project with a couple of corrections, now something draws, but it's not what I expect. It's a single colour where I've used multiple, and it's in the top right when I expect it to be in the bottom left.
An apple guy posted the answer to my problems on the apple devforums.
The reason I couldn't get anything to draw was the order of the matrix and position multiplication matters in the vertex shader. So the line:
gl_Position = position * mvpMatrix;
Should be:
gl_Position = mvpMatrix * position;
EDIT: Removed second part of answer as per Reto's comment.
In Objective-C I can do the following operation:
Objective-C code:
CGFloat width = CGRectGetWidth(mView.bounds);
CGFloat total = width*[myArray count];
but in Swift, it will raise an error:
Could not find an overload for '*' that accepts the supplied arguments
How can I avoid this situation elegantly?
First, let's create some demo data
let array: NSArray = ["a", "b", "c"] //you could use a Swift array, too
let view = UIView() //just some view
Now, everything else works almost the same way as in Obj-C
let width: CGFloat = CGRectGetWidth(view.bounds)
or simply
let width = CGRectGetWidth(rect) //type of the variable is inferred
and total:
let total = width * CGFloat(array.count)
Note that we have to add a CGFloat cast for array.count. Obj-C would implicitly cast NSUInteger to CGFloat but Swift has no implicit casts, so we have to add an explicit one.
In Swift you cannot multiply two numbers of different types (NSNumber, Int, Double, etc.) directly. The width of a CGRect is of floating point type and the array count is of integer type. Here's a working example:
let myArray: Int[] = [1,2,3]
let rect: CGRect = CGRect(x:0,y:0,width:100,height:100)
let total: Double = rect.size.width * Double(myArray.count)
Swift does not allow operations between two numbers of different types. Therefore, before to multiply your array.count (Int) by your width (CGFloat), you'll have to cast it to CGFloat.
Fortunately, Swift provides a simple CGFloat initializer init(_:) that creates a new CGFloat from an Int. This initializer has the following declaration:
init<Source>(_ value: Source) where Source : BinaryInteger
Creates a new value, rounded to the closest possible representation.
The Swift 5 Playground sample code below shows how to perform your calculation by using CGFloat's initializer:
import UIKit
import CoreGraphics
// Set array and view
let array = Array(1...3)
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
let view = UIView(frame: rect)
// Perform operation
let width = view.bounds.width
let total = width * CGFloat(array.count)
print(total) // prints: 300.0
need to change All Int to CGFloat Type, or change All CGFloat to Int
let a: CGFloat = 0.25
let b: Int = 1
// wrong: Binary operator '*' cannot be applied to operands of type 'CGFloat' and 'Int'
// let c = a * b
// right: change All Int to CGFloat Type
let r = a * CGFloat(b)
// right: change All CGFloat to Int
let r = Int(a) * b