How to unit test UIView's frame after applying CGAffineTransform(translationX) - ios

I want to test the following method using Quick:
func initial(states: UIView...) {
for view in states {
view.transform = CGAffineTransform(translationX: 0, y: 20)
}
}
This is the code I am using for testing (simplified version):
class ProtocolsSpec: QuickSpec {
override func spec() {
var view1: UIView!
var view2: UIView!
describe("Animator") {
beforeEach {
view1 = UIView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
view2 = UIView(frame: CGRect(x: 80, y: 80, width: 50, height: 50))
}
it("views origin should be (0, 20) at startup") {
initial(states: view1, view2)
expect(view1.frame.origin.x) == 0
expect(view1.frame.origin.y) == 20
}
}
}
}
The tests doesn't work, the x and y are not changed.
My question is how to get the correct (x,y) coordinates after applying the transformation ?

The frame, center, and bounds properties will not change after applying transformations. The documentation of frame even states that:
If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
API Reference
In order to get the effective frame I'd suggest applying the views transform onto the views frame (using the applying(_:) method on CGRect. In your case that would look like:
let resultingFrame = view1.frame.applying(view1.transform)
expect(resultingFrame.origin.x) == 0
expect(resultingFrame.origin.y) == 20

Related

object value doesn't change after didset

I am using some cocoa pods frame to make UICollectionView. I am able to get the right data for each cell and setup views.
I want to have different ani
The animation end point should be related to the value of dataSource.The problem is that I can't use the didSet value as I wish. The frame of subviews I added are (0,0,0,0), the values of objects keep the same as initial value outside the didSet. I am not able to access the dataSource Value in setupView function.
Expected effect is that the red bar will move from left side to the right position:
class CereCell : DatasourceCell {
override var datasourceItem: Any?{
didSet {
guard let cere = datasourceItem as? CeremonyDay else { return }
nameLabel.text = cere.name
nameLabel.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
currentBar.frame = CGRect(x: 100, y: 100, width: cere.percentage*100 , height: 10)
position.x = cere.percentage
}
}
let currentBar: UIView = {
let currentBar = UIView(frame: CGRect(x: 290, y: 100, width: 300, height: 10))
currentBar.backgroundColor = .red
return currentBar
}()
override func setupViews() {
super.setupViews()
contentView.addSubview(currentBar)
let frame = currentBar.frame
print(frame)
It just print the (290, 100, 300, 10), which is not the value in didSet.

Swift : drawing the dots in loop

I am trying to draw multiple dots in a loop by passing set of points. But not a single dot is getting drawn on view. Idea - I am parsing xml document and extracting point to draw it on view.
Edit - I have updated the code with suggested changes and it is working.
//View Class
class DrawTrace: UIView
{
var points = [CGPoint]()
{
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect)
{
let size = CGSize(width: 1, height: 1)
UIColor.white.set()
for point in points
{
print(point.x,point.y)
let dot = UIBezierPath(ovalIn: CGRect(origin: point, size: size))
dot.fill()
}
}
}
//View Controller Class
class ViewController: UIViewController
{
var object : traceDoc = traceDoc()
let trace = DrawTrace(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
override func viewDidLoad()
{
super.viewDidLoad()
object.collectionOfData()
trace.points = object.tracePoints
self.view.addSubview(trace)
}
}
Added view instance to view hierarchy that is in view controller.Created instance of DrawTrace and appended to tracepoints array.
Your code is way more complex than it needs to be. One big issue is that you keep adding more and more layers each time draw is called.
There is no need to use layers to draw dots in your custom view. Here is a much simpler solution:
class DrawTrace: UIView
{
var points = [CGPoint]() {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect)
{
let size = CGSize(width: 2, height: 2)
UIColor.black.set()
for point in points {
let dot = UIBezierPath(ovalIn: CGRect(origin: point, size: size))
// this just draws the circle by filling it.
// Update any properties of the path as needed.
// Use dot.stroke() if you need an outline around the circle.
dot.fill()
}
}
}
let trace = DrawTrace(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
trace.backgroundColor = .white
trace.points = [ CGPoint(x: 10, y: 10), CGPoint(x: 35, y: 100)]
The above can be copied into a playground.
Black dotes on black view aren't visible, try to make dotes white:
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
Also, in UIViewController code you are not adding your DrawTrace to views hierarchy, try in viewDidLoad:
self.view.addSubview(drawTraceInstance)
Finally, it seems that you have to iterate over array of CGPoint's. Instead of use stride, user FastEnumeration over Array:
for point in points
{
drawDot(x: point.x, y: point.y,Color: fillColor, size: size)
}

Getting details about the pinch gesture in ARKit

In my project I have a pinch to resize option for the object that has been placed in scene view. But when someone pinch the screen to reduce or enlarge the actual size of the object I need to get that scale. I need to display the scale in which the object is being changed in the screen. How do I get the scale when the action is being performed?
Thank you
Within your main ViewController Class for the ARSCNView
declare the label view, and the label itself at the top.
let scaleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, 70))
let labelView = UIView(frame: CGRect(x: 300, y: 300, width: 300, height: 70))
Now within LoadView or ViewDidLoad you can set the attributes for the label such backgroundColor, textColor etc... and also add the view and label to sceneView.
// add your attributes for label,view
labelView.backgroundColor = .clear
scaleLabel.textColor = .white
scaleLabel.adjustsFontSizeToFitWidth
// add you views to sceneView
labelView.addSubview(scaleLabel)
sceneView.addSubview(labelView)
Lastly, with the pinch gesture function for scaling.. which should look something like this.
#objc func pinchGesture(_ gesture: UIPinchGestureRecognizer) {
if nodeYouScale != nil {
let action = SCNAction.scale(by: gesture.scale, duration: 0.1)
nodeYouScale.runAction(action)
gesture.scale = 1
// this part updates the label with the current scale factor
scaleLabel.text = "X: \(nodeYouScale.scale.x) Y: \(nodeYouScale.scale.y) Z:\(nodeYouScale.scale.z)"
} else {
return
}

Programmatic UIView with subview not rendering in correct position

I'm trying to render a view that has a subview in it. However, the subview renders in the incorrect y-position.
ViewController is the parent view.
ViewController2 is a subview of ViewController and has it's own subview (let's call it X). X is being rendered in the incorrect y-position, even though ViewController2 and X have the same y value for their frames.
Edit:
I should note, X should be appear in the same position within ViewController2. Or at least, that's the intention.
See code below:
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
var y: CGFloat = 10
for vc in getVcs() {
self.view.addSubview(vc.render(x: 10, y: y))
y += 75
}
super.viewDidLoad()
}
func getVcs() -> [ViewController2] {
return [
ViewController2(),
ViewController2(),
ViewController2()
]
}
}
ViewController2.swift
import UIKit
class ViewController2: UIViewController {
func render(x: CGFloat, y: CGFloat) -> UIView {
let viewFrame = CGRect(x: x, y: y, width: 300, height: 50)
let xview = UIView(frame: viewFrame)
let subViewFrame = CGRect(x: x, y: y, width: 200, height: 25) // X
let subView = UIView(frame: subViewFrame)
subView.layer.borderColor = UIColor.green.cgColor
subView.layer.borderWidth = 1
xview.addSubview(subView)
xview.layer.borderColor = UIColor.red.cgColor
xview.layer.borderWidth = 1
return xview
}
}
How they appear at runtime:
(Green border represents X and the red border represents ViewController2.)
Any help would be appreciated!
A view defines its own frame in relation to its parent view.
In ViewController2, the subview you instantiated based on y parameter is getting added in relation to its parent view (the red box).
The solution is to change y value in relation to parent frame on your subview frame
let subViewFrame = CGRect(x: x, y: y, width: 200, height: 25) // X
Also to get a clear idea what's happening, try adding another ViewController2() in getVCs() and it will look like this.
As you can see, your code is not misplacing the second view as it looks like from your screen shot. It's placing the green box further and further in relation to its parent frame. Third frame was just a lucky hit. Hope this helps =)
Frames are specified in their SUPERVIEW'S COORDINATES. Your bounds are in the current view's coordinates. Therefore ViewController2's frame needs to be expressed as relative to ViewController 's frame, not relative to the window.

How do I change UIView Size?

I have trouble with modifying my UIView height at launch.
I have to UIView and I want one to be screen size * 70 and the other to fill the gap.
here is what I have
#IBOutlet weak var questionFrame: UIView!
#IBOutlet weak var answerFrame: UIView!
let screenSize:CGRect = UIScreen.mainScreen().bounds
and
questionFrame.frame.size.height = screenSize.height * 0.70
answerFrame.frame.size.height = screenSize.height * 0.30
It has no effect on the app during run time.
I use autolayout but I only have margins constraints...
Am I doing it all wrong?
This can be achieved in various methods in Swift 3.0 Worked on Latest version MAY- 2019
Directly assign the Height & Width values for a view:
userView.frame.size.height = 0
userView.frame.size.width = 10
Assign the CGRect for the Frame
userView.frame = CGRect(x:0, y: 0, width:0, height:0)
Method Details:
CGRect(x: point of X, y: point of Y, width: Width of View, height:
Height of View)
Using an Extension method for CGRECT
Add following extension code in any swift file,
extension CGRect {
init(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) {
self.init(x:x, y:y, width:w, height:h)
}
}
Use the following code anywhere in your application for the view to set the size parameters
userView.frame = CGRect(1, 1, 20, 45)
Here you go. this should work.
questionFrame.frame = CGRectMake(0 , 0, self.view.frame.width, self.view.frame.height * 0.7)
answerFrame.frame = CGRectMake(0 , self.view.frame.height * 0.7, self.view.frame.width, self.view.frame.height * 0.3)
Swift 3 and Swift 4:
myView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
Hi create this extends if you want. Update 2021 Swift 5
Create File Extends.Swift and add this code (add import foundation where you want change height)
extension UIView {
var x: CGFloat {
get {
self.frame.origin.x
}
set {
self.frame.origin.x = newValue
}
}
var y: CGFloat {
get {
self.frame.origin.y
}
set {
self.frame.origin.y = newValue
}
}
var height: CGFloat {
get {
self.frame.size.height
}
set {
self.frame.size.height = newValue
}
}
var width: CGFloat {
get {
self.frame.size.width
}
set {
self.frame.size.width = newValue
}
}
}
For Use (inherits Of UIView)
inheritsOfUIView.height = 100
button.height = 100
print(view.height)
For a progress bar kind of thing, in Swift 4
I follow these steps:
I create a UIView outlet : #IBOutlet var progressBar: UIView!
Then a property to increase its width value after a user action var progressBarWidth: Int = your value
Then for the increase/decrease of the progress progressBar.frame.size.width = CGFloat(progressBarWidth)
And finally in the IBACtion method I add progressBarWidth += your value for auto increase the width every time user touches a button.
You can do this in Interface Builder:
1) Control-drag from a frame view (e.g. questionFrame) to main View, in the pop-up select "Equal heights".
2)Then go to size inspector of the frame, click edit "Equal height to Superview" constraint, set the multiplier to 0.7 and hit return.
You'll see that constraint has changed from "Equal height to..." to "Proportional height to...".
Assigning questionFrame.frame.size.height= screenSize.height * 0.30 will not reflect anything in the view. because it is a get-only property. If you want to change the frame of questionFrame you can use the below code.
questionFrame.frame = CGRect(x: 0, y: 0, width: 0, height: screenSize.height * 0.70)
I know that there is already a solution to this question, but I found an alternative to this issue and maybe it could help someone.
I was having trouble with setting the frame of my sub view because certain values were referring to its position within the main view. So if you don't want to update your frame by changing the whole frame via CGRect, you can simply change a value of the frame and then update it.
// keep reference to you frames
var questionView = questionFrame.frame
var answerView = answerFrame.frame
// update the values of the copy
questionView.size.height = CGFloat(screenSize.height * 0.70)
answerView.size.height = CGFloat(screenSize.height * 0.30)
// set the frames to the new frames
questionFrame.frame = questionView
answerFrame.frame = answerView

Resources