Adding UIButtons in an arc - ios

I'm trying to make a button that when tapped forms an arc of options. I'm able to draw the options on a diagonal line (see image below) but can't figure out how to display them in an arc. Any suggestions?
What I'm getting
Code
let resourceObjects = ["sprite", "shape", "text", "function", "sound", "variable", "list"]
let radius : CGFloat = 120
var shiftX : CGFloat = 0
var shiftY : CGFloat = radius
// x^2 + y^2 = radius
for o in resourceObjects {
let cell = UIButton()
cell.frame = CGRect(x: sender.frame.origin.x + shiftX, y: sender.frame.origin.y + 50 - shiftY, width: screenWidth, height: 30)
self.view.addSubview(cell)
let img = UIImageView()
img.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
img.image = UIImage(named: String(o) + ".png")
cell.addSubview(img)
let lbl = UILabel()
lbl.frame = CGRect(x: 27, y: 0, width: screenWidth - 60, height: 30)
lbl.font = UIFont.systemFont(ofSize: 12)
lbl.text = o
lbl.sizeToFit()
lbl.frame = CGRect(x: 27, y: 0, width: lbl.frame.width, height: lbl.frame.height)
cell.addSubview(lbl)
cell.frame = CGRect(x: sender.frame.origin.x + shiftX, y: sender.frame.origin.y + 50 - shiftY, width: lbl.frame.width + 27, height: 30)
shiftX += radius / CGFloat(resourceObjects.count)
shiftY -= radius / CGFloat(resourceObjects.count)
options.append(cell)
}

Use trig to calculate the positions: vary your angle from 0 to 2 pi for a full circle. Use a fraction of that range for a part of a circle.
x = cos(angle) * radius + origin.x
y = sin(angle) * radius + origin.y

Related

Swift Some Lottie Animations Distorted

I am using Lottie in my iOS app, and have changed the colors of all animations to fit my color palatte. All animations are json file that i download directly from the lottie editor. The lottie editor shows an animation working, but the result is distorted for some and fine for others, using the same method. Is there something wrong with the JSON that i can fix?
func expandModal() {
if state != .contracted {
return
}
dismissMe = false
contractedFrame = self.frame
solutionLabel.alpha = 0
state = .gettingBigger
let animation = Animation.named(name)
animationView.animation = animation
animationView.loopMode = .loop
animationView.frame = leftArrow.frame
leftArrow.alpha = 0
addSubview(animationView)
UIView.animate(withDuration: 0.5) { [self] in
frame = CGRect(x: 0, y: 0, width: 300, height: 400)
center = self.superview!.center
layer.cornerRadius = 10
leftArrow.frame = CGRect(x: 10, y: 20, width: modalType == .Points ? 100 : 150, height: modalType == .Points ? 100 : 150)
leftArrow.center.x = frame.width / 2
animationView.frame = leftArrow.frame
titleLabel.alpha = 1
titleLabel.numberOfLines = 2
titleLabel.frame = CGRect(x: 24*kWidthFactor, y: leftArrow.frame.maxY + 24 * kHeightFactor, width: self.frame.width - 48 * kWidthFactor, height: 1000)
titleLabel.font = sfUITextBold(fontSize: 24 * kHeightFactor)
titleLabel.textAlignment = .center
titleLabel.sizeToFit()
titleLabel.center.x = self.frame.width / 2
addSubview(titleLabel)
descriptionLabel.alpha = 1
descriptionLabel.numberOfLines = 0
descriptionLabel.frame = CGRect(x: 24*kWidthFactor, y: titleLabel.frame.maxY + 12 * kHeightFactor, width: self.frame.width - 48 * kWidthFactor, height: 1000)
descriptionLabel.text = descriptionString
descriptionLabel.font = sfUITextRegular(fontSize: 18 * kHeightFactor)
descriptionLabel.sizeToFit()
addSubview(descriptionLabel)
} completion: { [self] _ in
self.state = .expanded
animationView.play()
}
}
Pathfinder json image editor
Architect json image
Video of app

macOS NSScrollView Not Scrolling Vertically

I am trying to add few views to see if the NSScrollView will scroll vertically but it is not doing anything.
private func configure2() {
var yOffset = 0
let scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 400, height: 900))
scrollView.hasVerticalScroller = true
for _ in 1...20 {
let v = NSView(frame: NSRect(x: 0, y: 0 + yOffset, width: 50, height: 20))
v.wantsLayer = true
v.layer?.backgroundColor = NSColor.red.cgColor
scrollView.addSubview(v)
yOffset += 40
}
scrollView.backgroundColor = NSColor.green
self.addSubview(scrollView)
}
I remember in UIKit I can set the contentSize property of UIScrollView but in macOS I cannot set contentSize.
You need to set either the documentView or contentView of your NSScrollView. Then, you'll add your subviews to that view.
private func configure2() {
var yOffset = 0
let scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 400, height: 900))
let documentView = NSView(frame: .zero)
scrollView.hasVerticalScroller = true
for _ in 1...20 {
let v = NSView(frame: NSRect(x: 0, y: 0 + yOffset, width: 50, height: 20))
v.wantsLayer = true
v.layer?.backgroundColor = NSColor.red.cgColor
documentView.addSubview(v)
yOffset += 40
}
print(yOffset)
documentView.frame = .init(x: 0, y: 0, width: 400, height: yOffset)
scrollView.documentView = documentView
scrollView.backgroundColor = NSColor.green
self.addSubview(scrollView)
}

Offset 2D-Matrix: equal spacing in pattern

Let's first take a look at the Screenshot:
My problem:
I just cannot figure out how to change the spacing between the horizontal lines. This spacing is a little bit too wide, as you can see compared to the vertical lines.
This is how I implemented the pattern of UIButtons:
func buildBoard(){
for i in 0...8{
for j in 0...8{
rowBtnArr.append(setupBtn(x: i, y: j))
}
}
}
func setupBtn( x: Int, y: Int)->UIButton{
let btnSize = Int(UIScreen.main.bounds.width / 10) //40
let buffer = 1
if(y%2==0){ //even lines: normal
button = UIButton(frame: CGRect(x: x*btnSize, y: y*btnSize, width: btnSize-buffer, height: btnSize-buffer))
}else{ //odd lines: shift by 1/2 button width
button = UIButton(frame: CGRect(x: x*btnSize+(btnSize/2), y: y*btnSize, width: btnSize-buffer, height: btnSize-buffer))
}
button.layer.cornerRadius = 0.5 * button.bounds.size.width
//unimportant color and target lines are left out
return button
}
Thanks for any help!
SwiftHobby
I think one thing you have to consider is that because you are offsetting the rows every even row, there is more space vertically because the peak height of your circles matches up with the intersection of circles above it. So what I would do is subtract some number to the btnSize before you multiply it by y. This should tighten the y-spacing.
func setupBtn( x: Int, y: Int)->UIButton{
let btnSize = Int(UIScreen.main.bounds.width / 10) //40
let buffer = 1
if(y%2==0){ //even lines: normal
button = UIButton(frame: CGRect(x: x*btnSize, y: y*(btnSize-1), width: btnSize-buffer, height: btnSize-buffer))
}else{ //odd lines: shift by 1/2 button width
button = UIButton(frame: CGRect(x: x*btnSize+(btnSize/2), y: y*(btnSize-1), width: btnSize-buffer, height: btnSize-buffer))
}
button.layer.cornerRadius = 0.5 * button.bounds.size.width
//unimportant color and target lines are left out
return button
}
This way the y-spacing is closer than the x-spacing, accounting for the mismatch I was talking about earlier. You need to experiment with the y-spacing to get the visual difference you want, so my correction above might be weird looking as well.
maybe you want this effect:
I use your code and make some changes, and use a Array to cache all buttons's frame like blow:
// user a Array to cache all frames
lazy var framesMatrix: [[CGRect]] = Array(repeating: Array(repeating: CGRect.zero, count: 9), count: 9)
func setupBtn( x: Int, y: Int)->UIButton{
let btnCount = 10
let buffer: CGFloat = 1
let btnSize = (UIScreen.main.bounds.width - CGFloat(btnCount - 1) * buffer) / CGFloat(btnCount)
var button: UIButton = UIButton()
if y % 2 == 0 {
if y == 0 {
let frame = CGRect(x: CGFloat(x)*(btnSize+buffer), y: CGFloat(y)*btnSize, width: btnSize, height: btnSize)
framesMatrix[x][y] = frame
button = UIButton(frame: frame)
} else {
let lastFrame = framesMatrix[x][y-1]
// Here is the change
let newCenterY = lastFrame.midY + sqrt(3.0) * 0.5 * (btnSize+buffer);
let frame = CGRect(x: lastFrame.minX - btnSize * 0.5 , y: newCenterY - btnSize * 0.5, width: btnSize, height: btnSize);
framesMatrix[x][y] = frame
button = UIButton(frame: frame)
}
} else {
let lastFrame = framesMatrix[x][y-1]
// Here is the change
let newCenterY = lastFrame.midY + sqrt(3.0) * 0.5 * (btnSize+buffer);
let frame = CGRect(x: lastFrame.midX + buffer * 0.5, y: newCenterY - btnSize * 0.5 , width: btnSize, height: btnSize);
framesMatrix[x][y] = frame
button = UIButton(frame: frame)
}
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.backgroundColor = UIColor.black
//unimportant color and target lines are left out
return button
}

Swift Error - 'Cannot invoke value of type 'SwiftStockChart.LabelForValueGetter' (aka'(Int) -> String') with argument list '(Value:Int)'

I am getting an error when I am trying to set the chart points from the Yahoo Finance API using Swift.
The error says
Cannot invoke value of type 'SwiftStockChart.LabelForValueGetter'
(aka'(Int) -> String') with argument list '(Value:Int)'
And this is on line.
let text = labelForIndex(index: i)
The full code is:
func setChartPoints(points: [ChartPoint]) {
if points.isEmpty { return }
dataPoints = points
computeBounds()
if maxValue!.isNaN { maxValue = 1.0 }
for i in 0 ..< verticalGridStep! {
let yVal = axisHeight! + margin! - CGFloat((i + 1)) * axisHeight! / CGFloat(verticalGridStep!)
let p = CGPoint(x: (valueLabelPosition! == .right ? axisWidth! : 0), y: yVal)
let text = labelForValue(value: minValue! + (maxValue! - minValue!) / CGFloat(verticalGridStep!) * CGFloat((i + 1)))
let rect = CGRect(x: margin!, y: p.y + 2, width: self.frame.size.width - margin! * 2 - 4.0, height: 14.0)
let width = text.boundingRect(with: rect.size,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes:[NSFontAttributeName : valueLabelFont!],
context: nil).size.width
let xPadding = 6
let xOffset = width + CGFloat(xPadding)
let label = UILabel(frame: CGRect(x: p.x - xOffset + 5.0, y: p.y, width: width + 2, height: 14))
label.text = text
label.font = valueLabelFont
label.textColor = valueLabelTextColor
label.textAlignment = .center
label.backgroundColor = valueLabelBackgroundColor!
self.addSubview(label)
axisLabels.append(label)
}
for i in 0 ..< horizontalGridStep! + 1 {
let text = labelForIndex(index: i)
let p = CGPoint(x: margin! + CGFloat(i) * (axisWidth! / CGFloat(horizontalGridStep!)) * 1.0, y: axisHeight! + margin!)
let rect = CGRect(x: margin!, y: p.y + 2, width: self.frame.size.width - margin! * 2 - 4.0, height: 14)
let width = text.boundingRect(with: rect.size,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes:[NSFontAttributeName : indexLabelFont!],
context: nil).size.width
let label = UILabel(frame: CGRect(x: p.x - 5.0, y: p.y + 5.0, width: width + 2, height: 14))
label.text = text
label.font = indexLabelFont!
label.textAlignment = .left
label.textColor = indexLabelTextColor!
label.backgroundColor = indexLabelBackgroundColor!
self.addSubview(label)
axisLabels.append(label)
}

Quality of scaled-down UILabels

I'm using Swift 3 to create an iOS interface where some UIViews containing (amongst other things) UILabels are scaled up and down based on where they're being positioned on the screen. My first approach was to create and populate the views at a comfortably large size (say 100x100) and then scale them as needed using CGAffineTransform(scaleX:y:), however I've noticed that the downscaling of the text in the labels isn't graceful at all, and the text becomes pixelated and close to unreadable at small scales. As a comparison (see example below), changing the font size directly gives much better results, however the structure within my views is somewhat complex and having to redraw everything based on some size factor would be a hassle. Is there a better and smoother way to approach this problem?
Here's an example project I've created to illustrate the problem, as well as the output in the simulator (same as on the iPhone itself), downscaled views are on the left (red) and changed font sizes are the right (green).
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for i in 1...10 {
let f = CGFloat(1.0) / CGFloat(i)
let view1 = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 50))
view1.backgroundColor = UIColor.red
let label1 = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 50))
label1.text = "\(100 / i)%"
label1.font = UIFont(name: "Verdana", size: 24.0)
label1.textAlignment = .right
view1.addSubview(label1)
view1.transform = CGAffineTransform(scaleX: f, y: f)
view1.center = CGPoint(x: 160 - 75.0 * f, y: CGFloat(60 * i) + 25.0 * f)
self.view.addSubview(view1)
let view2 = UIView(frame: CGRect(x: CGFloat(170), y: CGFloat(60 * i), width: 150 * f, height: 50 * f))
view2.backgroundColor = UIColor.green
let label2 = UILabel(frame: CGRect(x: 0, y: 0, width: 150 * f, height: 50 * f))
label2.text = "\(100 / i)%"
label2.font = UIFont(name: "Verdana", size: 24.0 * f)
view2.addSubview(label2)
self.view.addSubview(view2)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This might be an answer - but not really suitable for a comment, so...
Give this a try - it creates a 3rd "column" of yellow-background views, using .adjustsFontSizeToFitWidth. The font size will auto-adjust based on the size of the views that contain the labels.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for i in 1...10 {
let f = CGFloat(1.0) / CGFloat(i)
let view1 = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 50))
view1.backgroundColor = UIColor.red
let label1 = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 50))
label1.text = "\(100 / i)%"
label1.font = UIFont(name: "Verdana", size: 24.0)
label1.textAlignment = .right
view1.addSubview(label1)
view1.transform = CGAffineTransform(scaleX: f, y: f)
view1.center = CGPoint(x: 160 - 75.0 * f, y: CGFloat(60 * i) + 25.0 * f)
self.view.addSubview(view1)
let view2 = UIView(frame: CGRect(x: CGFloat(170), y: CGFloat(60 * i), width: 150 * f, height: 50 * f))
view2.backgroundColor = UIColor.green
let label2 = UILabel(frame: CGRect(x: 0, y: 0, width: 150 * f, height: 50 * f))
label2.text = "\(100 / i)%"
label2.font = UIFont(name: "Verdana", size: 24.0 * f)
view2.addSubview(label2)
self.view.addSubview(view2)
let view3 = UIView(frame: CGRect(x: CGFloat(270), y: CGFloat(60 * i), width: 150 * f, height: 50 * f))
view3.backgroundColor = UIColor.yellow
let label3 = UILabel(frame: view3.bounds)
label3.text = "\(100 / i)%"
label3.font = UIFont(name: "Verdana", size: 24.0)
label3.adjustsFontSizeToFitWidth = true
label3.minimumScaleFactor = 0.05
label3.numberOfLines = 0
// we want the label to resize with the view, if the view frame changes
label3.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view3.autoresizesSubviews = true
view3.addSubview(label3)
self.view.addSubview(view3)
}
}

Resources