I am trying to dynamically arrange set of image data in an array on a UIViewI want four per role and max role is 12 making 4 per role. This is the problem, I can I split the array and place the images evenly where each and everyone should be. Eg:
12 Images
======|=====|======|======|
| | | |
| | | |
======|=====|======|======|
| | | |
| | | |
======|=====|======|======|
| | | |
| | | |
|=====|=====|======|======|
I know how to arrange the image, but then splitting the array is the problem. What if I have 7 images in the array that would be 4 at the top, 3 at the bottom or 10, which would be 4,4,2. I don't know how to go about it. That's why I don't have any code pasted at all. Any help on splitting and arranging the array would be appreciated. Thanks
Storing values in a single array and using the array as a data source for a 2D structure is fairly common. You can get the X and Y positions based on the index of the item and the width and height of the structure.
// number of images across the view
let width = 4;
// number of images down the view
let height = 3;
// index of the image in the array
let index = 10;
// index of the space from the left
// starting with 0
let xLocation = index % width; // 2
// index of the space from the top
// starting with 0
let yLocation = index / width; // 2
A function that returns the values as a tuple is below. All that you'd need to do is iterate over your array and it will return the positions of the items based on the provided information.
func getPosition(width:Int, index:Int) -> (Int,Int) {
return (index % width, index / width);
}
edit: code clarification
So, this is what I did. I wrote a function that extends Array to split my Array evenly
extension Array {
func splitBy(subSize: Int) -> [[Element]] {
return 0.stride(to: self.count, by: subSize).map { startIndex in
let endIndex = startIndex.advancedBy(subSize, limit: self.count)
return Array(self[startIndex ..< endIndex])
}
}
}
Then, I did the bit of displaying the images with this
private func setGridView(gridCell: ProfileGrid, indexPath: NSIndexPath) {//140813008
let (location, photoCount, images) = dummyGrid[indexPath.row]
gridCell.imageCount.text = photoCount
gridCell.location.text = location
if images.count > 0 && images.count <= 4 {
for index in 0..<images.count {
let dedicatedWidth = self.view.frame.size.width/4 - 8
let imageView = UIImageView(frame: CGRect(x: (dedicatedWidth + 2) * CGFloat(index), y: 0, width: CGFloat(dedicatedWidth - 2), height: 80))
imageView.image = UIImage(named: images[index])
imageView.layer.cornerRadius = 5
imageView.clipsToBounds = true
gridCell.imageHolder.addSubview(imageView)
}
} else if images.count > 4 && images.count <= 8 {
let split = images.splitBy(4)
let firstSplit = split[0]
let secondSplit = split[1]
for index in 0..<firstSplit.count {
let dedicatedWidth = self.view.frame.size.width/4 - 8
let imageView = UIImageView(frame: CGRect(x: (dedicatedWidth + 2) * CGFloat(index), y: 0, width: CGFloat(dedicatedWidth - 2), height: 80))
imageView.image = UIImage(named: firstSplit[index])
imageView.layer.cornerRadius = 5
imageView.clipsToBounds = true
gridCell.imageHolder.addSubview(imageView)
}
for jDex in 0..<secondSplit.count {
let dedicatedWidth = self.view.frame.size.width/4 - 8
let imageView = UIImageView(frame: CGRect(x: (dedicatedWidth + 2) * CGFloat(jDex), y: 82, width: CGFloat(dedicatedWidth - 2), height: 80))
imageView.image = UIImage(named: secondSplit[jDex])
imageView.layer.cornerRadius = 5
imageView.clipsToBounds = true
gridCell.imageHolder.addSubview(imageView)
}
} else if images.count > 8 {
let split = images.splitBy(4)
let firstSplit = split[0]
let secondSplit = split[1]
let thirdSplit = split[2]
for index in 0..<firstSplit.count {
let dedicatedWidth = self.view.frame.size.width/4 - 8
let imageView = UIImageView(frame: CGRect(x: (dedicatedWidth + 2) * CGFloat(index), y: 0, width: CGFloat(dedicatedWidth - 2), height: 80))
imageView.image = UIImage(named: firstSplit[index])
imageView.layer.cornerRadius = 5
imageView.clipsToBounds = true
gridCell.imageHolder.addSubview(imageView)
}
for jDex in 0..<secondSplit.count {
let dedicatedWidth = self.view.frame.size.width/4 - 8
let imageView = UIImageView(frame: CGRect(x: (dedicatedWidth + 2) * CGFloat(jDex), y: 82, width: CGFloat(dedicatedWidth - 2), height: 80))
imageView.image = UIImage(named: secondSplit[jDex])
imageView.layer.cornerRadius = 5
imageView.clipsToBounds = true
gridCell.imageHolder.addSubview(imageView)
}
for tDex in 0..<thirdSplit.count {
let dedicatedWidth = self.view.frame.size.width/4 - 8
let imageView = UIImageView(frame: CGRect(x: (dedicatedWidth + 2) * CGFloat(tDex), y: 164, width: CGFloat(dedicatedWidth - 2), height: 80))
imageView.image = UIImage(named: thirdSplit[tDex])
imageView.layer.cornerRadius = 5
imageView.clipsToBounds = true
gridCell.imageHolder.addSubview(imageView)
}
}
}
If you've a better of doing it, I don't mind. My code is too big and kinda not professional from my on look of it. Ideas please
Related
Im working on a SpriteKit game and the player starts with three hearts, when he gets hit he loses one and if he collects a heart item he can get a heart back. I got it all to work but when he gets a heart back they appear around the last heart.
// 1: here is the function for when he loses a heart
func takeDamage() {
if invulnerable || damaged { return }
lives -= 1
let lastElementIndex = heartsArray.count - 1
print(heartsArray.count)
if heartsArray.indices.contains(lastElementIndex - 1) {
let lastHeart = heartsArray[lastElementIndex]
lastHeart.removeFromParent()
print("removed heart")
heartsArray.remove(at: lastElementIndex)
}
// 2: here is the original filling of hearts
func fillHearts(count: Int) {
for index in 1...count {
let heartTexture = SKTexture(imageNamed: "heart")
let heart = SKSpriteNode(imageNamed: "heart")
heart.size = CGSize(width: heartTexture.size().width / 4, height: heartTexture.size().height / 4)
let xPosition = heart.size.width * CGFloat(index - 1)
heart.position = CGPoint(x: xPosition, y: 0)
heartsArray.append(heart)
heartContainer.addChild(heart)
}
// 3: and here is the adding + 1 heart when he gets a heart item
if refillHeart == true {
for index in 0..<1 {
let heartTexture = SKTexture(imageNamed: "heart")
let heart = SKSpriteNode(imageNamed: "heart")
heart.size = CGSize(width: heartTexture.size().width / 4, height: heartTexture.size().height / 4)
let xPosition = heart.size.width * CGFloat(index - 1)
heart.position = CGPoint(x: xPosition, y: 0)
heartsArray.append(heart)
heartContainer.addChild(heart)
print(heartsArray)
refillHeart = false
}
}
That's because in your for loop, the index is always 0, so the xPosition will always be 0.
if refillHeart == true
{
let heartTexture = SKTexture(imageNamed: "heart")
let heart = SKSpriteNode(imageNamed: "heart")
heart.size = CGSize(width: heartTexture.size().width / 4, height: heartTexture.size().height / 4)
// this was the problem
// let xPosition = heart.size.width * CGFloat(index - 1)
// because of your for loop, the index was always 0
// get number of displayed hearts
let heartsNumber: CGFloat = CGFloat(heartsArray.count)
let xPosition = heart.size.width * heartsNumber
heart.position = CGPoint(x: xPosition, y: 0)
heartsArray.append(heart)
heartContainer.addChild(heart)
print(heartsArray)
refillHeart = false
}
I've created a Graphic Class to show for user a graphic points choose accordingly with user information.
If I use the graphicView object and add points at viewDidLoad, the graphic is presented correctly, if not there, the graphic presents no data.
See below the code for the Graphics and the code when I am requesting to mark the points.
class GraphView: UIView {
private struct Constants {
static let cornerRadiusSize = CGSize(width: 8.0, height: 8.0)
static let margin: CGFloat = 40.0
static let topBorder: CGFloat = 30
static let bottomBorder: CGFloat = 40
static let colorAlpha: CGFloat = 0.3
static let circleDiameter: CGFloat = 5.0
}
//1 - the properties for the gradient
var startColor: UIColor = UIColor.rgb(red: 14, green: 40, blue: 80)
var endColor: UIColor = UIColor.rgb(red: 14, green: 40, blue: 80)
//Weekly sample data
var graphPoints: [Int] = [0]
override func draw(_ rect: CGRect) {
let width = rect.width
let height = rect.height
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: UIRectCorner.allCorners,
cornerRadii: Constants.cornerRadiusSize)
path.addClip()
//2 - get the current context
let context = UIGraphicsGetCurrentContext()!
let colors = [startColor.cgColor, endColor.cgColor]
//3 - set up the color space
let colorSpace = CGColorSpaceCreateDeviceRGB()
//4 - set up the color stops
let colorLocations: [CGFloat] = [0.0, 1.0]
//5 - create the gradient
let gradient = CGGradient(colorsSpace: colorSpace,
colors: colors as CFArray,
locations: colorLocations)!
//6 - draw the gradient
var startPoint = CGPoint.zero
var endPoint = CGPoint(x: 0, y: self.bounds.height)
context.drawLinearGradient(gradient,
start: startPoint,
end: endPoint,
options: CGGradientDrawingOptions(rawValue: 0))
//calculate the x point
let margin = Constants.margin
let columnXPoint = { (column:Int) -> CGFloat in
//Calculate gap between points
let spacer = (width - margin * 2 - 4) / CGFloat((self.graphPoints.count - 1))
var x: CGFloat = CGFloat(column) * spacer
x += margin + 2
return x
}
// calculate the y point
let topBorder: CGFloat = Constants.topBorder
let bottomBorder: CGFloat = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnYPoint = { (graphPoint:Int) -> CGFloat in
var y:CGFloat = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
y = graphHeight + topBorder - y // Flip the graph
return y
}
// draw the line graph
UIColor.white.setFill()
UIColor.white.setStroke()
//set up the points line
let graphPath = UIBezierPath()
//go to start of line
graphPath.move(to: CGPoint(x:columnXPoint(0), y:columnYPoint(graphPoints[0])))
//add points for each item in the graphPoints array
//at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
graphPath.addLine(to: nextPoint)
}
//Create the clipping path for the graph gradient
//1 - save the state of the context (commented out for now)
context.saveGState()
//2 - make a copy of the path
let clippingPath = graphPath.copy() as! UIBezierPath
//3 - add lines to the copied path to complete the clip area
clippingPath.addLine(to: CGPoint(x: columnXPoint(graphPoints.count - 1), y:height))
clippingPath.addLine(to: CGPoint(x:columnXPoint(0), y:height))
clippingPath.close()
//4 - add the clipping path to the context
clippingPath.addClip()
let highestYPoint = columnYPoint(maxValue)
startPoint = CGPoint(x:margin, y: highestYPoint)
endPoint = CGPoint(x:margin, y:self.bounds.height)
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: 0))
context.restoreGState()
//draw the line on top of the clipped gradient
graphPath.lineWidth = 3.0
graphPath.stroke()
//Draw the circles on top of graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point.x -= Constants.circleDiameter / 2
point.y -= Constants.circleDiameter / 2
let circle = UIBezierPath(ovalIn: CGRect(origin: point, size: CGSize(width: Constants.circleDiameter, height: Constants.circleDiameter)))
circle.fill()
}
//Draw horizontal graph lines on the top of everything
let linePath = UIBezierPath()
//top line
linePath.move(to: CGPoint(x:margin, y: topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y:topBorder))
//center line
linePath.move(to: CGPoint(x:margin, y: graphHeight/2 + topBorder))
linePath.addLine(to: CGPoint(x:width - margin, y:graphHeight/2 + topBorder))
//bottom line
linePath.move(to: CGPoint(x:margin, y:height - bottomBorder))
linePath.addLine(to: CGPoint(x:width - margin, y:height - bottomBorder))
let color = UIColor(white: 1.0, alpha: Constants.colorAlpha)
color.setStroke()
linePath.lineWidth = 1.0
linePath.stroke()
}
}
I am trying to mark the points accordingly with the user input - see below:
func budgetAvailableCalculationFunction() {
let bankValue = (userSalary as NSString).integerValue
let bankPorcentage: Int = 100
let expenses = (userExpenses as NSString).integerValue
let calculation1: Int = expenses * bankPorcentage
let calculation2: Int = calculation1 / bankValue
let cashPorcentageAvailable = calculation2
let value: [Int] = [expenses]
self.setupGraphy(points: value)
progressView.progress = 0.0
progress.completedUnitCount = Int64(cashPorcentageAvailable)
progressView.setProgress(Float(self.progress.fractionCompleted), animated: true)
progressView.progressTintColor = UIColor.rgb(red: 239, green: 75, blue: 92)
progressView.backgroundColor = UIColor.rgb(red: 239, green: 75, blue: 92)
progressView.trackTintColor = .white
progressView.clipsToBounds = false
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.layer.cornerRadius = 0
porcentageLabelForMonth.text = "\(Int(self.progress.fractionCompleted * 100)) %"
}
The setupGraphy is just a function that returns an array of indexes that the user adds it.
Please note that using the same function at the viewDidLoad works:
self.setupGraphy(points: [100, 400, 200, 250])
enter image description here
Anyone?
Code for viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.rgb(red: 245, green: 246, blue: 250)
tableView.delegate = self
tableView.dataSource = self
tableView.register(LastTransactionsCell.self, forCellReuseIdentifier: "LastTransactionsCell")
tableView.backgroundColor = .clear
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy - LLLL"
let nameOfMonth = dateFormatter.string(from: now)
currentMonthLabel.text = nameOfMonth
setupUIXViews()
fetchUserInfo()
//static data for now
*self.setupGraphy(points: [100, 400, 200, 250])*
DispatchQueue.main.asyncAfter(deadline: .now() + 2){
self.budgetAvailableCalculationFunction()
}
}
The way I managed to fix (seems like a temporary fix) is by creating a new Int variable at the top of the project and passing the fetch data from the DB to these variables. That way I can use them at viewDidLoad instead of inside the fetch function.
I am trying to simply show a video removing part of its background which is meant to be transparent. I've tried several approaches courtesy of Stack Overflow, and so far they are all sub-par. The one that came closest to the results I'm seeking is the one in this link: ARKit / SpriteKit - set pixelBufferAttributes to SKVideoNode or make transparent pixels in video (chroma-key effect) another way
However, as sensible as the approach is, and as much as it seems to be working for the person who asked the question, in my case it only turns the whole video white.
My strategy is to show the video and add the effect as follows:
func setVideoNode(named name: String, in node: SCNNode, with imageReference: ARReferenceImage?, size: CGSize = CGSize(width: 500, height: 320), extension ext: String = "mp4") {
let nodeWidth = imageReference!.physicalSize.width
let nodeHeigth = imageReference!.physicalSize.height
guard let videoUrl = Bundle.main.url(forResource: name, withExtension: ext) else {
print("Guard Fail")
return
}
self.currentPlayer = AVPlayer(url: videoUrl)
let videoNode = SKVideoNode(avPlayer: self.currentPlayer)
videoNode.size = size
videoNode.name = name
videoNode.yScale = -1.0
videoNode.play()
let effectNode = SKEffectNode()
effectNode.addChild(videoNode)
effectNode.filter = colorCubeFilterForChromaKey(hueAngle: 0)
let planeGeometry = SCNPlane(width: nodeWidth, height: nodeHeigth)
planeGeometry.firstMaterial?.diffuse.contents = effectNode
planeGeometry.firstMaterial?.isDoubleSided = true
let planeNode = SCNNode()
planeNode.geometry = planeGeometry
planeNode.position = SCNVector3(planeNode.position.x + 1, 0.5, 0)
planeNode.eulerAngles.x = -.pi / 2
node.addChildNode(planeNode)
self.currentPlayer.play()
}
func RGBtoHSV(r : Float, g : Float, b : Float) -> (h : Float, s : Float, v : Float) {
var h : CGFloat = 0
var s : CGFloat = 0
var v : CGFloat = 0
let col = UIColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0)
col.getHue(&h, saturation: &s, brightness: &v, alpha: nil)
return (Float(h), Float(s), Float(v))
}
func colorCubeFilterForChromaKey(hueAngle: Float) -> CIFilter {
let hueRange: Float = 20 // degrees size pie shape that we want to replace
let minHueAngle: Float = (hueAngle - hueRange/2.0) / 360
let maxHueAngle: Float = (hueAngle + hueRange/2.0) / 360
let size = 64
var cubeData = [Float](repeating: 0, count: size * size * size * 4)
var rgb: [Float] = [0, 0, 0]
var hsv: (h : Float, s : Float, v : Float)
var offset = 0
for z in 0 ..< size {
rgb[2] = Float(z) / Float(size) // blue value
for y in 0 ..< size {
rgb[1] = Float(y) / Float(size) // green value
for x in 0 ..< size {
rgb[0] = Float(x) / Float(size) // red value
hsv = RGBtoHSV(r: rgb[0], g: rgb[1], b: rgb[2])
// TODO: Check if hsv.s > 0.5 is really nesseccary
let alpha: Float = (hsv.h > minHueAngle && hsv.h < maxHueAngle && hsv.s > 0.5) ? 0 : 1.0
cubeData[offset] = rgb[0] * alpha
cubeData[offset + 1] = rgb[1] * alpha
cubeData[offset + 2] = rgb[2] * alpha
cubeData[offset + 3] = alpha
offset += 4
}
}
}
let b = cubeData.withUnsafeBufferPointer { Data(buffer: $0) }
let data = b as NSData
let colorCube = CIFilter(name: "CIColorCube", parameters: [
"inputCubeDimension": size,
"inputCubeData": data
])
return colorCube!
}
In my case, I'm trying to remove reds. Is there any other way to achieve this?
Hi dear friend as you seen in this link
.You must be use no between 330 to 360 degree for remove red color.
effectNode.filter = colorCubeFilterForChromaKey(hueAngle: 330)
I am trying to generate a section of image views which will sit at the top of an application and update as the user progresses through the quiz.
My array variable is Qs, the code I have to generate an imageView is as follows:
var imageView: UIImageView!
var i = 0
var total = Int(Qs.capacity) // Just testing what .capacity does.
for i in 0..<(Qs.count-1){
imageView - UIImageView(frame: CGRect(x: 0, y: 75, width: 50, height: 50))
imageView.image = UIImage(named:"InfoIcon")
imageView.contentMode = .scaleAspectFit
self.view.addSubView(imageView)
}
I already have a variable which tracks the users progress through the quiz with just an integer if that would be any help, its declared along with the quiz functionality.
Here is a fantastically draw visual of what I am trying to accomplish:
Any help is appreciated,
thanks
Lets assume you have a container view attached at the top, then you can use this method to add any number of imageView's.
func addProgressImages(to view: UIView, count: Int) {
let screenSize = UIScreen.main.bounds.size
for i in 0..<count {
let spacing: CGFloat = 2
let tag = i + 888
let width: CGFloat = screenSize.width/CGFloat(count)
let height: CGFloat = width
if let view = view.viewWithTag(tag) {
view.removeFromSuperview()
}
let x = spacing + CGFloat(i) * width
let imageView = UIImageView(frame: CGRect(x: x, y: 100, width: width - spacing, height: height))
imageView.image = UIImage(named:"InfoIcon")
imageView.tag = tag
imageView.contentMode = .scaleAspectFit
view.addSubview(imageView)
}
}
You can play with y value for vertical alignment.
I'm struggling to get all the coupons (UIImage) to show up. For example, I have 3 coupons, when I run my code, it keeps on showing up the 3rd coupon's image only. How come it doesn't show the first 2? Thanks in advance.
override func viewDidLoad() {
var i: Int = 0
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
CouponsViewController.PopulateDataCoupons()
i=0
repeat {
let imageName = MyCoupons.siteCoupons[i]//"Coupons1.jpg"
let image = UIImage(named: imageName!)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 50, y: 50, width: 350, height: 300)
view.addSubview(imageView)
i += 1 }
while (i < MyCoupons.count-1)
}
You are putting each UIImageView object at the same location. Look at this line:
imageView.frame = CGRect(x: 50, y: 50, width: 350, height: 300)
Try changing the x or y coordinate on each iteration to see each one of the images, like this:
let height = 350
i = 0
repeat {
let imageName = MyCoupons.siteCoupons[i]//"Coupons1.jpg"
let image = UIImage(named: imageName!)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 50, y: i * height, width: 350, height: height)
view.addSubview(imageView)
i += 1
}
while (i < MyCoupons.count - 1)
Update:
As #Mick MacCallum points out. You should take the UIImageView's height into consideration. See the changes above.