How to declare properties with a for loop in Xcode - ios

I have about 80 CALayer properties I need to add to a UIViewController and I'm wondering if anyone knows a way to do this with a for loop as opposed to me copying and pasting 80 lines of code like this:
var colorButton1 = CALayer()
var colorButton2 = CALayer()
var colorButton1 = CALayer()
var colorButton2 = CALayer()
var colorButton1 = CALayer()
var colorButton2 = CALayer()
........(colorbuttons go to 60)
I know how to make the objects with a for loop in the viewDidLoad method, like this:
let colorLayers = 60
var colorLayer = CALayer()
var buttonYPosition:CGFloat = 0.0
for colorNum in 1...60 {
buttonYPosition = buttonYPosition + 50
colorLayer.contents = UIImage(contentsOfFile:NSBundle.mainBundle().resourcePath!.stringByAppendingPathComponent("color\(colorNum).png"))!.CGImage
colorLayer.frame = CGRectMake(device.x/2, buttonYPosition, worldScale * 380, worldScale * 180)
colorLayer.zPosition = 6.0
self.view.layer.addSublayer(colorLayer)
}
but these are not properties.
So how can I put something like this in the top of my script so it creates 60 properties with 60 different indexed names, as opposed to 60 objects with the same name?
I'm not even sure this is possible, I'm just hoping to condense my code.

When dealing with many objects of the same type, rather than declaring all of them separately, you should make an array of that object type. In the case of CALayer, you can initialize an array of 60 objects using the count:repeatedValue initializer like so:
var layers = [CALayer](count: 60, repeatedValue: CALayer())
override func viewDidLoad() {
super.viewDidLoad()
// Access all layers
for layer in layers {
// Do something
}
// Access an individual layer
layers[0].frame = CGRectZero
}
Notice you access individual layers using subscript notation, which is cleaner than doing layer1, layer2, layer3, etc...

Use an array of CALayers:
var colorLayers = [CALayer]()

Related

How can I create a Pie Chart with rounded slice edges?

I want to achieve the following effect, shown below in my iOS app (written in Swift)
So far I have been able to achieve this, using Charts by danielgindi. But I am not able to get the desired effect as I want to. Is there any way to add rounded corners to each Pie Chart slice like in this example image here?:
My Chart setup is as follows:
let data1 = PieChartDataEntry(value: 3)
let data2 = PieChartDataEntry(value: 5)
let data3 = PieChartDataEntry(value: 4)
let data4 = PieChartDataEntry(value: 6)
let data5 = PieChartDataEntry(value: 8)
let values = [data1, data2, data3, data4, data5]
let chartDataSet = PieChartDataSet(entries: values, label: nil)
let chartData = PieChartData(dataSet: chartDataSet)
let colors = [UIColor.fuelTintColor, UIColor.maintenanceTintColor, UIColor.insuranceTintColor, UIColor.fastagTintColor, UIColor.miscTintColor]
chartDataSet.colors = colors as! [NSUIColor]
chartDataSet.sliceSpace = 10
pieChart1.data = chartData
pieChart1.holeRadiusPercent = 0.8
I think it can be done using the PieChartRenderer but I have no idea how I should proceed.
Also, if you have any suggestions for other ways to implement this do let me know.
Solved it, and here's the output:
ring-chart-img
Basically I am using this library, called CircularProgressView (https://cocoapods.org/pods/CircleProgressView) to achieve the individual rings. Since I needed 5 rings, I stacked 5 such views (with clear background) on top of each other and rotated them to achieve my desired effect.
Cocoapod setup
First, you have to install the CircularProgressView pod in your project. I am using Cocoapods for importing the library here.
NOTE: If you do not have Cocoapods setup already then you need to install Cocoapods first using steps here: https://guides.cocoapods.org/using/getting-started.html
To add it using Cocoapods, add the following line in your Podfile.
pod 'CircleProgressView'
and run pod install on the Terminal from within your project directory.
Interface Builder
In your Storyboard file, go to your View Controller and add a view and set its contents. You can also create this view programmatically but here I will be using the storyboard.
Create 4 more views and use Auto Layout to stack them on top of one another.
Select all 5 views and go to the Identity Inspector (right panel in storyboard, 4th item in top bar).
Set the Class field value (under Custom Class) as 'CircularProgressView'.
Link all 5 views to your ViewController.swift file.
I have named them as follows:
#IBOutlet weak var pieChart1: CircleProgressView!
#IBOutlet weak var pieChart2: CircleProgressView!
#IBOutlet weak var pieChart3: CircleProgressView!
#IBOutlet weak var pieChart4: CircleProgressView!
#IBOutlet weak var pieChart5: CircleProgressView!
Call the function showRingChart() in viewDidLoad() to setup the views.
Here is the code for the functions:
// Function to convert degrees to radian
func degToRad(_ rotationDegrees: Double) -> CGFloat {
let rotationAngle = CGFloat(rotationDegrees * .pi/180.0)
return rotationAngle
}
// Function to Show Ring Chart
func showRingChart() {
// Values for the graph, can be changed as per your need
let val1 = self.val1
let val2 = self.val2
let val3 = self.val3
let val4 = self.val4
let val5 = self.val5
let totalVal = (val1 + val2 + val3 + val4 + val5)
var spacing = 0.05 * totalVal // Spacing is set to 5% (ie. 0.05). Change it according to your needs
print("Spacing: ", spacing)
let totalSpacing = 5 * spacing //Total spacing value to be added in the chart
let total = totalVal + totalSpacing //Total corresponding to 100% on the chart
if val1 == 0.0
&& val2 == 0.0
&& val3 == 0.0
&& val4 == 0.0
&& val5 == 0.0 {
// NO DATA, HIDE ALL CHARTS
pieChart1.isHidden = true
pieChart2.isHidden = true
pieChart3.isHidden = true
pieChart4.isHidden = true
pieChart5.isHidden = true
} else {
// DATA AVAILABLE
// Calculate Percentage of each value in the ring chart (ie. progress)
let valOnePerc = (val1 / total)
let valTwoPerc = (val2 / total)
let valThreePerc = (val3 / total)
let valFourPerc = (val4 / total)
let valFivePerc = (val5 / total)
let spacingPerc = spacing / total
// Angle offsets (in degrees)
let offset1 = (valOnePerc + spacingPerc) * 360
let offset2 = (valOnePerc + valTwoPerc + (2 * spacingPerc)) * 360
let offset3 = (valOnePerc + valTwoPerc + valThreePerc + (3 * spacingPerc)) * 360
let offset4 = (valOnePerc + valTwoPerc + valThreePerc + valFourPerc + (4 * spacingPerc)) * 360
print("--- PRINTING CHART VALUES HERE ---- ")
print(total)
print(valOnePerc)
print(valTwoPerc)
print(valThreePerc)
print(valFourPerc)
print(valFivePerc)
print(offset1)
print(offset2)
print(offset3)
print(offset4)
print("------------------")
// Setup ring chart sections
pieChart1.trackFillColor = UIColor.tintColorOne
pieChart1.progress = valOnePerc
pieChart2.trackFillColor = UIColor.tintColorTwo
pieChart2.progress = valTwoPerc
pieChart2.transform = CGAffineTransform(rotationAngle: degToRad(offset1))
pieChart3.trackFillColor = UIColor.tintColorThree
pieChart3.progress = valThreePerc
pieChart3.transform = CGAffineTransform(rotationAngle: degToRad(offset2))
pieChart4.trackFillColor = UIColor.tintColorFour
pieChart4.progress = valFourPerc
pieChart4.transform = CGAffineTransform(rotationAngle: degToRad(offset3))
pieChart5.trackFillColor = UIColor.tintColorFive
pieChart5.progress = valFivePerc
pieChart5.transform = CGAffineTransform(rotationAngle: degToRad(offset4))
// Unhide all charts
pieChart1.isHidden = false
pieChart2.isHidden = false
pieChart3.isHidden = false
pieChart4.isHidden = false
pieChart5.isHidden = false
}
}
It should do it. Change the (val1, val2, ..., val5) values to change the progress of the rings.

Avoid redundant dereferencing in iOS

Suppose I want to change size of an uiView: UIView to w,h. I can do it like that:
uiView.frame.size.width = w
uiView.frame.size.height = h
In another system I can avoid replication of dereferencing (which means waste of both size and performance) by keeping a reference in a variable (using Swift syntax):
let ref = uiView.frame.size
ref.width = v
ref.height = h
This however doesn't work in iOS, where CGSize is a structure and therefore is copied when assigned to another value.
Is there a way to avoid redundant dereferencing (something like with(uiView.frame.size){...} available in some languages)
I don't think there is a way to do it exactly because the frame is a value-copied structure. You could set the frame directly as Reiner Melian suggests, but to me that seems even longer and uses dereferencing at least the same amount of time as your approach.
There is a way how to make it simpler this using extensions, but behind the scenes it will again be using dereferencing:
extension UIView {
var width: CGFloat {
get {
return self.frame.size.width
}
set {
self.frame.size.width = newValue
}
}
var height: CGFloat {
get {
return self.frame.size.height
}
set {
self.frame.size.height = newValue
}
}
}
And then you could use:
uiView.width = w
uiView.height = h
on any UIView instance.
This is even simpler:
uiView.frame.size = CGSize(width: w, height: h)
As I understand it, RHS is a temporary value released as soon as the content has been copied to frame structure.

how to set frame dynamically in swift?

i am trying to set imageview as subview for scrollview.for that i am setting different frame depends upon the screensize.
This is my code
let screensize:CGRect=UIScreen.mainScreen().bounds
let screenwidth=screensize.width
var frames:CGRect
for var index=0 ;index < arrBirds.count ;index++
{
if(screenwidth==320)
{
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);
frames.origin.y = 0;
frames.size=scrollview.frame.size;
}
else if(screenwidth==375)
{
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);
frames.origin.y=0;
frames.size=scrollview.frame.size;
}
else
{
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);
frames.origin.y=0;
frames.size=scrollview.frame.size;
}
imageView=UIImageView(frame:frames)
imageView.image=UIImage(named: arrBirds[index] as! String)
scrollview .addSubview(imageView)
But i am getting error this line
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);//struct frame must be completely initialised before a member is stored to.
You have not yet created an instance of the struct CGRect yet. Before accessing / setting any members on it you you need to create a instance first and only after that set the different members of it:
Change the line var frames:CGRect to:
var frames = CGRectZero
According to Apple Docs :
Classes and structures must set all of their stored properties to an
appropriate initial value by the time an instance of that class or
structure is created.
Your frames object isn't initialized, instead you should write :
var frames = CGRectZero

don't know how to update the score label node , when all my nodes are declared locally

i created a game where a ball passes through obstacles that fall down in random positions , and when the ball passes through an obstacle the score should be increased by one , but all the nodes of objects are declared locally and don't how to increase the score with out creating so many nodes.
here is an example: of one of my functions:
func leftObject(){
var rand = arc4random_uniform(2) + 1
if rand == 1
{
var bigWall = SKSpriteNode(imageNamed: "shortwall")
bigWall.position = CGPointMake(CGRectGetMinX(self.frame) + bigWall.size.width / 2, CGRectGetMaxY(self.frame))
var moveObjects = SKAction.moveByX(0, y: -self.frame.size.height * 2, duration: NSTimeInterval(self.frame.size.height / 100))
var removeObjects = SKAction.removeFromParent()
var moveAndRemoveObjects = SKAction.sequence([moveObjects,removeObjects])
bigWall.runAction(moveAndRemoveObjects)
bigWall.physicsBody = SKPhysicsBody(rectangleOfSize: bigWall.size)
bigWall.physicsBody?.dynamic = false
bigWall.physicsBody?.categoryBitMask = objectGroup
}
else
{
var tallWall = SKSpriteNode(imageNamed: "shortwall")
tallWall.position = CGPointMake(CGRectGetMinX(self.frame) + tallWall.size.width / 2, CGRectGetMaxY(self.frame))
var moveObjects = SKAction.moveByX(0, y: -self.frame.size.height * 2, duration: NSTimeInterval(self.frame.size.height / 100))
var removeObjects = SKAction.removeFromParent()
var moveAndRemoveObjects = SKAction.sequence([moveObjects,removeObjects])
tallWall.runAction(moveAndRemoveObjects)
tallWall.physicsBody = SKPhysicsBody(rectangleOfSize: tallWall.size)
tallWall.physicsBody?.categoryBitMask = objectGroup
tallWall.physicsBody?.dynamic = false
movingObjects.addChild(tallWall)
}
and then i have a function that is being called every 1 second that generates a random number to call the 6 functions that displays the objects randomly.
if you know a way to do it even if i have to change my code , please help.
Thank you.
you have a few options here, as well as some code design decisions.
First, any code that is duplicate should be put into a separate method instead of sitting in your if/else. From the looks of it, your first 8 lines or so are duplicate. so make a new method:
func setUpWall() -> SKSpriteNode {
var tallWall = SKSpriteNode(imageNamed: "shortwall")
//all other lines that are duplicate;
}
this way you call this method in both your if/else conditions:
var rand = arc4random_uniform(2) + 1
var wall = setUpWall()
if rand == 1 {
//any unique stuff
} else {
//other unique stuff
}
secondly, to be able to access your wall or any variable outside of the method you can declare them as instance variables at the top of the class
var wall = SKSpriteNode() //its declared as var, so you can update it inside the method call and still access it outside the method
this is one way, another way would be to have a map or dictionary of some sort to hold your local variable in it. The dictionary has a key value pair, so you can assign a specific key as the name of your variable, and assign the right object to it as a value
you can read about them here, but I'll provide an example below:
https://developer.apple.com/library/mac/documentation//General/Reference/SwiftStandardLibraryReference/Dictionary.html
//declare your dictionary as an instance variable (a class variable, not inside a method)
//pseudocode, you gotta make sure the syntax is correct and it compiles
var myDict = Dictionary<String: SKSpriteNode>()
and in your method when you create any sprites that you want to access outside of the method, add them to the dictionary and give them a name that you will know:
var bigWall = SKSpriteNode(imageNamed: "shortwall")
myDict["myWall1"] = bigWall
//then to get it later you can say:
let myWallFromOtherMethod = myDict["myWall1"]
//but I would check if the key exists first
// you don't want to get a nil error
// so use this syntax
if let myWallFromOtherMethod = myDict["myWall1"] {
// now val is not nil and the Optional has been unwrapped, so use it
}
Like I said before, you need to make sure this all complies with the syntax I've added because I haven't personally tested it in Xcode. Also, since I don't know how your overall project is designed, one method may work better for you than the other.

How to create a variable number of UIImageViews in swift

My problem is that based on the number of groups a person has I want to dynamically create that many UIImageView and assign them to the view. I know how to create a single UIImageView and put it onto the view but how can I create a variable number of them since creating another with the same name seems to just overwrite the original? I tried sending the UIImageView into an Array but got an optional error.
circleView = UIImageView(frame:CGRectMake(125, y, 75, 75))
y+=150
circleView.image = UIImage(named:"circle.png")
circleView.tag = i
self.view.addSubview(circleView)
circleArray[i] = circleView
The problem is that you keep reusing the same circleView instance. When you add it to the array, you are just adding a reference to it. You then reinitialize it, which wipes it out. I also don't see where you are incrementing i. Try something like this:
var circleArray = Array<UIImageView>()
var y : CGFloat = 0.0
var i = 0
circleArray.append(UIImageView(frame:CGRectMake(125, y, 75, 75)))
var circleView = circleArray[i]
circleView.image = UIImage(named:"circle.png")
circleView.tag = i
self.view.addSubview(circleView)
y+=150
i += 1
You need to use circleArray.append(circleView)

Resources