New view.frame.y doesn't match up with label.frame.y - ios

I have a label on-screen that has its height set as 300px, set to the bottom of the screen, and I want to create a UIView that exactly overlays it. The code I use is
let theFrame = CGRect(x: myLabel.frame.minX, y: myLabel.frame.minY, width: myLabel.frame.width, height: myLabel.frame.height)
newVu = UIView(frame: theFrame)
let newColor = UIColor(colorLiteralRed: 0.5, green: 0.5, blue: 1, alpha: 0.5)
newVu.backgroundColor = newColor
self.view.addSubview(newVu)
The result that I get has the new UIView shifted about 20 points higher than the label. ((EDIT: as GeorgeGreen helped me see in Comments, this is because the label is within a Vertical StackView, which is constrained to the bottom of the Top Layout Guide -- thus the Y of the label is relative to the top of the Stack View, not the top of the screen.)) Clearly there are different frames of reference at work, but what is needed to bring everything into the same frame of reference? EDIT: Or, asked another way, how can I get the "absolute" X and Y coordinates of the label in the screen, so that no matter how many views it is embedded in, I can know where to drop a new View to exactly overlay it?
Original screen in IB:
Result of the code (new view is semi-transparent; note the yellow band at the bottom):

You can get your label position in view by
let theFrame = view.convert(myLabel.frame, from: your_stack_view_here)
and You should move it to
viewDidAppear
Because
viewWillLayoutSubviews
will call any time subview will layout. That's mind at the first time it run, your "mylabel" didn't layout with correct position.
Apple document

Regarding your question of “how can I get the "absolute" X and Y coordinates of the label in the screen”, the answer is to use UIView.convert(_:to:), passing nil as the second parameter. (This will yield coordinates relative to the containing window's origin, but for practical purposes is probably what you mean by “the screen”).

Related

How I can restrict position view while moving/dragging on PDFView? (Swift)

I took one custom view (SignatoryView) on pdf, When I click on Add button from Navigation bar, it will add that SignatoryView on PDF view and according to my choice I can move/drag that signatory view to any location.
Problem: When I am moving that signatory view on pdf, it is going outside edges of pdfView. (Left, right, bottom and top also)
It should not go beyond its boundaries, it should be movable only inside edges on PDF view.
How I can achieve this ? Here is the complete project code
You just need to get half of the width and half of the height of your signature and when setting the new center position of it add or subtract it from the origin x and/or y:
let minX = frame!.width/2
let minY = frame!.height/2
let maxX = pdfView.frame.width-minX
let maxY = pdfView.frame.height-minY
customView1?.center = CGPoint(
x: min(maxX, max(minX, touchLocation!.x)),
y: min(maxY ,max(minY, touchLocation!.y)))

How do you get the safe area coordinates of a view programmatically in iOS 14?

Put simply, I want to get the bounds of this blue rectangle using Swift code:
The coordinates of this rectangle are visible in the Size inspector:
In an empty App project in Xcode, I've tried using view.safeAreaLayoutGuide.layoutFrame to get the CGRect, but this property seems to contain the bounds of the entire view, not the safe area. Specifically, it returns 0.0, 0.0, 896.0 and 414.0 for minX, minY, width and height respectively.
I've also tried to get the top, bottom, left and right properties of view.safeAreaInsets, but this returns 0 for each of them.
I've also tried to get the view.superview in case there was another view on top, but it returned nil.
All these values come from the iPhone 11. I'm using Xcode 12.
First, get the safe area insets. (Wait until they are known before you get them.) Okay, what are they inset from? The view controller's view. So simply apply those insets to the view controller's view's bounds and you have the rect of your rectangle (in view coordinates).
So, I believe this is the rectangle you were looking for?
How I did that:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for v in self.view.subviews {
v.removeFromSuperview()
}
let v = UIView()
v.backgroundColor = .blue
self.view.addSubview(v)
// okay, ready? here we go ...
let r = self.view.bounds.inset(by:self.view.safeAreaInsets) // *
v.frame = r
}
Remember, that rect, r, is in view coordinates. If you need the rect in some other coordinate system, there are coordinate conversion methods you can call.

How to animate centered square to the top

I have a UIView in a portrait-only app.
The view is centered vertically and horizontally with AutoLayout ("manually" using storyboards).
The width equals the (main)view.width * 0.9
The height is the same size of the width (it is a square).
I want to tap a button inside this UIView and animate it only vertically until it reaches the top border of the screen (eg. height*0.9, 10 pts, whatever is possible).
When I click again, I want to reposition back the view to its original position (centered as it was when I first tapped).
During the transition the square should not be tappable.
After reading many posts I could not understand what's the best way to do this (I red mainly developers saying old techniques using centerX should be avoided and lamentations about some versions of the SO behaving in strange ways).
I suppose I should find a way to get the current "position" of the constraints and to assign a constraint the "final" position, but I was not able to do it.
Any help is appreciated
You are all going the wrong way about this.
Add one constraint that pins the view to the top, and add one constraint that pins the view to centerY. It will complain, so pick one and disable it (I think the property in Interface Builder is called Installed).
If the initial state is the view in the center, disable the constraint that pins it to the top, and viceversa.
Now write IBOutlets for both constraints in your controller and connect them to those constraints. Make sure the declaration of that variable is not weak, otherwise the variable will become nil when the constraint is disabled.
Whenever you want to toggle your animation you can enable one constraint and disable the other.
#IBOutlet var topConstraint: NSLayoutConstraint!
#IBOutlet var centerConstraint: NSLayoutConstraint!
func toggleState(moveToTop: Bool) {
UIView.animateWithDuration(0.25) {
self.topConstraint.isActive = moveToTop
self.centerConstraint.isActive = !moveToTop
self.view.layoutIfNeeded()
}
}
While you can use Autolayout to animate - to take the constraint constraining the centerY and set its constant to a value that would move to the top (e.g., constant = -(UIScreen.main.bounds.height / 2)), I would recommend using view's transform property.
So to move the view to the top you can use:
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
.translatedBy(x: 0, y: -(viewHalfHeight - (boxHalfHeight + topMargin)))
}
You are moving box.center related to the view.center - so if you want to move the box to the top, you have to move its center by half a view's height (because the view's centerY is exactly height / 2 far from the view's top). That is not enough though, because then only a bottom half of the box is visible (now the box.centerY == view.top). Therefore you have to move it back by the box.bounds.height / 2 (in my code boxHalfHeight) - to make the top half visible. And to that boxHalfHeight you add topMargin so that there is some margin to the top.
Then, to move the box back to original position:
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
}
EDIT
If you really want to go with autolayout, you have to have a reference to the centerY constraint, so for example if it is created this way:
let boxCenterYConstraint = self.box.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
boxCenterYConstraint.isActive = true
Then you can try this:
// calculating the translation is the same
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
let diff = -(viewHalfHeight - (boxHalfHeight + topMargin))
boxCenterYConstraint.constant = diff
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
And animation back:
boxCenterYConstraint.constant = 0
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}

Swift UILabel with horizontal lines on both sides

I am trying to create a UILabel that has 2 horizontal lines on the left and right side like this:
Does anyone know the best approach for doing this in Swift? The content text in the center will change so I want to make sure it can adapt. I'd really like to create some kind of reusable UIView class but I'm not sure where to start?
Thank you!
You can take two UIview of height 1 or 2 pixels of both side of the label. so it's look likes line!!
And you should set background color to black of that view!
Hope this will help :)
Take one UIView with height of 2. Set leading & Trailing according to Super View.
Now take one UILabel with background color white and put Vertically Center to line view.
Make both Center same.
Your work done.
For more help please refer below image.
You can use an extension on UILabel
public extension UILabel {
func drawLineOnBothSides(labelWidth: CGFloat, color: UIColor) {
let fontAttributes = [NSFontAttributeName: self.font]
let size = self.text?.size(attributes: fontAttributes)
let widthOfString = size!.width
let width = CGFloat(1)
let leftLine = UIView(frame: CGRect(x: 0, y: self.frame.height/2 - width/2, width: labelWidth/2 - widthOfString/2 - 10, height: width))
leftLine.backgroundColor = color
self.addSubview(leftLine)
let rightLine = UIView(frame: CGRect(x: labelWidth/2 + widthOfString/2 + 10, y: self.frame.height/2 - width/2, width: labelWidth/2 - widthOfString/2 - 10, height: width))
rightLine.backgroundColor = color
self.addSubview(rightLine)
}
}
This will add a horizontal line of width of 1.0 on the both side of your label. If you don't add text for your label, it will show two horizontal lines through center with some spaces in between them.
As others have mentioned, you can achieve this using three views:
Add a View to your scene to use as a container. I called this view "Lined Label Holder."
To that container, add two Views, one to produce the line on either side of the label.
Add the label in between the two views, and give it some text. Due to the "height = Test.height" constraint on the Lined Label Holder, The intrinsic height of this label is used to calculate the container's height.
The label is allowed to grow with added text and the lines will always start 5px away from the edges of the text and extended to the edges of the container, whose width can be set independently.
This image shows the required constraints:
Use one UIView with black background and height of 1px, set label background to white, align its text to center and align UILabel to center of UIView (there is no need for 2 views since label white background will cover UIView).
Not necessary 2 UIView's.
Take 1 UIView and give background black color.Add the constraints necessary with: height=2.
place 1 label on the center and give required constraints

Swift: programmatically align labels for different IOS screen sizes

I'm new to Swift and ios development. I have 6 labels and a horizontal SKScene in my App. I would like to align those 6 labels beautifully and automatically. Now I have fixed the positions and the alignment always looks awful on some screen size while good on other.
I have not used storyboards or other graphical editors for building the ui but everything is done in code. Therefore I'm looking for a programmatic solution (code examples) for handling the alignment.
If you want to place them in the middle of the screen horizontally, but give them different y positions, you could just do something like this for each label:
label.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMaxY(self.frame) * 0.80)
To place them at different y positions, just multiply by the maxY by a different decimal number. This way, all of the labels are aligned along the x-axis and appear at different y-positions, like a column and they will appear this way on every screen size because they are positioned relative to the screen size and not in a fixed position.
You can align the labels (lets say at the center of the screen) like this.
var label1 = UILabel(CGRectMake: 0, 0, 200, 40)
label1.center = CGPointMake(UIScreen.mainScreen().bounds.size.width/2, 30)
var label2 = UILabel(CGRectMake: 0, 0, 200, 40)
label2.center = CGPointMake(UIScreen.mainScreen().bounds.size.width/2, label1.center.y + 30)
and so on. Just reference the main screen bounds and not static points for alignment, so that they are centered in any screen size.
What I ended up doing was to create an empty SKSprite to which I included the SKLabels. Now I can control by the pixed the distances between labels but align the top-level sprite in the middle of the screen despite screen size.

Resources