Offset 2D-Matrix: equal spacing in pattern - ios

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
}

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

Adding UIButtons in an arc

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

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)
}
}

How to loop Multiple UIButton in swift

I intended to make new position of my UIButton Frame by using loop but I got no luck, here is my code:
override func viewDidLoad() {
super.viewDidLoad()
for (var i = 75 ; i <= 275; ++i) {
let marginLoopButton: CGFloat = 175.0
let widthLoopButton = view.bounds.width - 2 * marginLoopButton
let marginYLoopButton: CGFloat = 295.0
let minimalYLoopButton = view.bounds.maxY - 2 * marginYLoopButton
self.loopButtonTwo.frame = CGRect(x: i, y: minimalYLoopButton, width: widthLoopButton, height: 70)
self.loopButtonTwo.setTitle("Orange", forState: UIControlState.Normal)
self.view.addSubview(self.loopButtonTwo)
}
}
so the first button position will be different than the second button. The problem is : self.loopButtonTwo.frame = CGRect(x: i, y: minimalYLoopButton, width: widthLoopButton, height: 70) When I put the i on x: Xcode gives me error message : "Cannot invoke initializer for type CGRect with an argument list of type (x: int, y:CGFloat, width"
and also how to multiply the number in swift? I know in C we just write for (i = 75 ; i <= 275; +=10){} but how to write in swift?
Anyone can help me?
Just cast i to CGFloat.
self.loopButtonTwo.frame = CGRect(x: CGFloat(i), y: minimalYLoopButton, width: widthLoopButton, height: 70)

IOS swift expandable view [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I would like to expand an view when I switch a switch to on, and to collapse when I switch the switch to off.
At the same time all the other elements I have in my design have to move down or up.
Here is how the layout looks like when the switches are OFF
And here is how the layout looks like when the switches are ON:
I hope you understand what I am trying to do.
And the question... How do I do that???
It's much easier to do with AutoLayout and inside InterfaceBuilder (with a xib or storyboard), so foremost familiarize yourself with them if needed.
Wrap a container UIView around the UISwitches and make sure to set it to Clip Subviews.
Add a constraint for the container view's height and set it to 0.
Drag that constraint into your class declaration to generate an IBOutlet property.
Drag a new outlet from your UISwitch 'Value Changed' event to the class to generate an IBAction method.
Make sure to implement that method so that when the value changes, based on the change you toggle-animate the height constraint (the IBOutlet)
self.firstHeightConstraint.constant = on ? 200.0 : 0.0
self.view.setNeedsUpdateConstraints()
UIView.animateWithDuration( ...
options:.BeginFromCurrentState,
animations: { self.view.layoutIfNeeded() } ...
)
Using UIView, it's not a good way. You should use UITableView instead of views.
Using TableView, you can add UISwitch with title according to the first image, in UITableView's header and return required number of rows in changed section in TableView on state ON and return the number of rows 0 on state OFF.
Still an issue, please let me know I'll help you.
I know my code isnt the cleanest, but it bothers me when answers tell the questioner its not a good way or do it this way instead. I am offering a solution NOT using table views or constraints:
In your example, you can use tags for each "level" that needs to be moved. So starting with switch 2 and all views below that, you would have a tag for that level (the second hidden collapsable view would have a tag of 2 as well), then switch 3 would have a tag of 3.
For the labels hidden, you are adding them to a subview i call insideView1 or insideView2. The frames of those inside views should have the proper minX, minY and width, but have a height of 0 to start. Also importantly they have layer.masksToBounds = false, so their subviews dont show when their height is 0. We then animate the height changing to the proper height (expanding), as well as all subviews under changing position.
Lastly i made a dictionary to keep track of when a section is expanded so you can use the same function to expand/collapse other areas while one is already expanded.
import UIKit
class ExpandViewController: UIViewController {
var insideView1 = UIView()
var insideView2 = UIView()
var expand = [1:true,2:true]
#objc func expandCollapse(_ sender : UIButton) {
var heightChange = CGFloat(0)
switch sender.tag {
case 1:
heightChange = 90
default:
heightChange = 90
}
var viewsToExpand = [UIView(),insideView1,insideView2]
UIView.animate(withDuration: 1.0) { [self] in
let expandMe = viewsToExpand[sender.tag]
if expand[sender.tag] == true {
expandMe.frame.size.height = heightChange
for v in view.subviews {
if v.tag > sender.tag {
v.center.y += heightChange
}
}
} else {
expandMe.frame.size.height = 0
for v in view.subviews {
if v.tag > sender.tag {
v.center.y -= heightChange
}
}
}
expand[sender.tag] = !expand[sender.tag]!
}
}
override func viewDidLoad() {
super.viewDidLoad()
let switcher1 = UISwitch()
switcher1.tag = 1
switcher1.isOn = false
switcher1.frame = CGRect(x: view.bounds.width - 150, y: 100, width: 40, height: 30)
switcher1.addTarget(self, action: #selector(expandCollapse(_:)), for: .allTouchEvents)
view.addSubview(switcher1)
let switchLabel1 = UILabel()
switchLabel1.tag = 1
switchLabel1.text = "Switch 1"
switchLabel1.frame = CGRect(x: 30, y: 0, width: view.bounds.width, height: 30)
switchLabel1.center.y = switcher1.center.y
view.addSubview(switchLabel1)
let switcher2 = UISwitch()
switcher2.tag = 2
switcher2.isOn = false
switcher2.frame = CGRect(x: view.bounds.width - 150, y: 140, width: 40, height: 30)
switcher2.addTarget(self, action: #selector(expandCollapse(_:)), for: .allTouchEvents)
view.addSubview(switcher2)
let switchLabel2 = UILabel()
switchLabel2.tag = 2
switchLabel2.text = "Switch 2"
switchLabel2.frame = CGRect(x: 30, y: 0, width: view.bounds.width, height: 30)
switchLabel2.center.y = switcher2.center.y
view.addSubview(switchLabel2)
let switcher3 = UISwitch()
switcher3.tag = 3
switcher3.isOn = false
switcher3.frame = CGRect(x: view.bounds.width - 150, y: 180, width: 40, height: 30)
// switcher3.addTarget(self, action: #selector(expandCollapse(_:)), for: .allTouchEvents)
view.addSubview(switcher3)
let switchLabel3 = UILabel()
switchLabel3.tag = 3
switchLabel3.text = "Switch 3"
switchLabel3.frame = CGRect(x: 30, y: 0, width: view.bounds.width, height: 30)
switchLabel3.center.y = switcher3.center.y
view.addSubview(switchLabel3)
insideView1.frame = CGRect(x: 0, y: switcher1.frame.maxY + 5, width: view.bounds.width, height: 0)
insideView1.layer.backgroundColor = UIColor.lightGray.cgColor
insideView1.layer.masksToBounds = true
insideView1.tag = 1
view.addSubview(insideView1)
insideView2.frame = CGRect(x: 0, y: switcher2.frame.maxY + 5, width: view.bounds.width, height: 0)
insideView2.layer.backgroundColor = UIColor.lightGray.cgColor
insideView2.layer.masksToBounds = true
insideView2.tag = 2
view.addSubview(insideView2)
let insideLabel1 = UILabel()
insideLabel1.text = "Label"
insideLabel1.frame = CGRect(x: 60, y: 10, width: view.bounds.width, height: 30)
insideView1.addSubview(insideLabel1)
let insideTextField1 = UITextField()
insideTextField1.frame = CGRect(x: 200, y: 0, width: view.bounds.width - 240, height: 30)
insideTextField1.center.y = insideLabel1.center.y
insideTextField1.layer.backgroundColor = UIColor.white.cgColor
insideView1.addSubview(insideTextField1)
let insideLabel2 = UILabel()
insideLabel2.text = "Label"
insideLabel2.frame = CGRect(x: 60, y: 50, width: view.bounds.width, height: 30)
insideView1.addSubview(insideLabel2)
let insideTextField2 = UITextField()
insideTextField2.frame = CGRect(x: 200, y: 0, width: view.bounds.width - 240, height: 30)
insideTextField2.center.y = insideLabel2.center.y
insideTextField2.layer.backgroundColor = UIColor.white.cgColor
insideView1.addSubview(insideTextField2)
let insideLabel3 = UILabel()
insideLabel3.text = "Label"
insideLabel3.frame = CGRect(x: 60, y: 10, width: view.bounds.width, height: 30)
insideView2.addSubview(insideLabel3)
let insideTextField3 = UITextField()
insideTextField3.frame = CGRect(x: 200, y: 0, width: view.bounds.width - 240, height: 30)
insideTextField3.center.y = insideLabel3.center.y
insideTextField3.layer.backgroundColor = UIColor.white.cgColor
insideView2.addSubview(insideTextField3)
let insideLabel4 = UILabel()
insideLabel4.text = "Label"
insideLabel4.frame = CGRect(x: 60, y: 50, width: view.bounds.width, height: 30)
insideView2.addSubview(insideLabel4)
let insideTextField4 = UITextField()
insideTextField4.frame = CGRect(x: 200, y: 0, width: view.bounds.width - 240, height: 30)
insideTextField4.center.y = insideLabel4.center.y
insideTextField4.layer.backgroundColor = UIColor.white.cgColor
insideView2.addSubview(insideTextField4)
}
}

Resources