How do you obtain the actual rendered value programmatically for an item that's positioned through autolayout? - swift

I currently have a logo horizontally centered on the storyboard, vertically positioned 50 pixels from the top of the story board, and it has a set width of 377 and a set height of 200. I am trying to set the bounds for another element programmatically using an IBOutlet tied to the logo, but the bounds that are being returned aren't the proper values, at least from what I've set in the story board.
I expect the values returned from logo.frame to be something like
x = 100, y = 50, width = 377, height = 200
but the actual values are
x = 0, y = 0, width = 240, height = 128
Is this because the logo has a dynamic positioning? Is there a different object I can use to get the values as they're rendered on the screen?

Have you tried to fetch the position after the layout has been changed, for example in viewDidAppear() ?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let width = logo.frame.width
}

Related

Swift 5: centerXAnchor won't center my frame in the view

I'm stuck at the Consolidation IV challenge from hackingwithswift.com.
Right now I'm trying to create a hangman game. I thought to place placeholder labels based on the length of the answer word. These placeholder labels would be placed inside a frame, which then would be placed in the center of the main view.
Unfortunately, the leading edge of the frame is placed centered. In my opinion, this is not a problem of constraints, but rather a problem of me creating the frame wrong.
My current code is
import UIKit
class ViewController: UIViewController {
var answer: String!
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - declare all the labels here
let letterView = UIView()
letterView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(letterView)
// MARK: - set constraints to all labels, buttons etc.
NSLayoutConstraint.activate([
letterView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
letterView.centerXAnchor.constraint(equalTo: view.layoutMarginsGuide.centerXAnchor)
])
// MARK: - populate the letterView
// set the size of the placeholder
answer = "Atmosphäre"
let height = 60
let width = 25
// var width: Int
for placeholder in 0..<answer.count {
// create new label and give it a big font size
let placeholderLabel = UILabel()
placeholderLabel.text = "_"
placeholderLabel.font = UIFont.systemFont(ofSize: 36)
// calculate frame of this label
let frame = CGRect(x: placeholder * width, y: height, width: width, height: height)
placeholderLabel.frame = frame
// add label to the label view
letterView.addSubview(placeholderLabel)
}
}
}
The simulator screen looks just like this:
I already searched for answers on stackoverflow, but wasn't successful. I think I don't know what I'm exactly looking for.
The main problem, is that the letterView has no size, because no width or height constraints are applied to it.
To fix your code make the letterView big enough to contain the labels you've added as subviews by adding height and width constraints after the for loop:
for placeholder in 0..<answer.count {
...
}
NSLayoutConstraint.activate([
letterView.widthAnchor.constraint(equalToConstant: CGFloat(width * answer.count)),
letterView.heightAnchor.constraint(equalToConstant: CGFloat(height))
])
I'm not sure if you've covered this in your course yet, but a better way to go about this (which would take much less code), is to use a UIStackView as your letterView instead.
An extra thing to consider:
If you give the letterView a background color, you'll see that the labels are actually aligned outside of its bounds:
That's because you're setting each label's y position to be height, when it should probably be zero:
let frame = CGRect(x: placeholder * width, y: 0, width: width, height: height)
Correcting this places the labels within the bounds of the letterView:

Setting view width constraint from custom UIView class not updating frames

I've got a UIView in which I want to have a simple loader view by using a percentage to determine the width.
I've got the percentage code completed and I know it's working.
However I'm having trouble setting the view's width constraint. I find the width by getting the frame width and multiplying it by the percentage. I know it's getting the right width. But I can't seem to set the constraint from this function.
My code goes like this in my UIView subclass:
func initSubviews() {
// in here i do some other stuff and have an asyncronous call to an api
// so then I've got this code calling the next function
DispatchQueue.main.async {
self.setCompletionWidth(nextTimer: nextTimer!, oldDate: oldDate!)
}
}
func setCompletionWidth(nextTimer: Date, oldDate: Date) {
let date = Date()
// calculatePercent returns something like 0.49
let percent = calculatePercent(middleDate: date, endDate: nextTimer, originalDate: oldDate)
//this is returning a correct value
let width = (self.frame.width)*percent
// completionView is the view I'm trying to change the width of
self.completionView.widthAnchor.constraint(equalToConstant: width)
self.layoutSubviews()
}
What's happening is that the completionView isn't getting the right width.
I've also tried in the setCompletionWidth function
self.completionView.widthAnchor.constraint(equalToConstant: width)
containerView.layoutSubviews()
and
UIView.animate(withDuration: 0.2) {
self.completionView.widthAnchor.constraint(equalToConstant: width)
self.containerView.layoutSubviews()
//also tried self.layoutSubviews here
}
and
self.layoutIfNeeded()
self.completionView.widthAnchor.constraint(equalToConstant: width)
self.layoutIfNeeded()
I'm expecting the width of the completionView to be something like 150, but instead it's 350, which is the original width it had.
I think what's happening is the view isn't updating after me setting the constraint to a different value. However, I can't for the life of me get it to update. I'd love some help here.
You need .isActive = true
self.completionView.widthAnchor.constraint(equalToConstant: width).isActive = true
Also to change the width you need to create a width var like
var widthCon:NSLayoutConstraint!
widthCon = self.completionView.widthAnchor.constraint(equalToConstant: width)
widthCon.isActive = true
Then change the constant with
widthCon.constant = /// some value
self.superview!.layoutIfNeeded()

How can I add a list of offset checkboxes to an NSScrollView?

I have an NSScrollView that I am trying to populate with check boxes (NSButton / NSToggleButton). The number of checkboxes I add should be equal to a number of items in an array. This part is working.
The following code successfully adds the checkboxes to the scroll view, but they are all on top of each other. I am trying to add them, but offset from each other in a "list view" manner such as a tableview on iOS. Here's my code:
for calendar in self.allCalendars {
self.allCalendars.append(calendar as EKCalendar)
let checkbox = NSButton.init(checkboxWithTitle: calendar.title, target: self, action: #selector(self.checkboxDidChange))
checkbox.setButtonType(NSToggleButton)
self.checkboxArray.append(checkbox as NSButton)
self.addSubview(checkbox)
}
And here's a screenshot of the result:
Here is something that you could implement, it would change how you do your loop so that you have an index of each iteration, but it can help you calculate some sort of vertical spacing:
for i in 0 ..< allCalendars.count {
let margin: CGFloat = 5.0 // Or whatever you want the padding on the left to be
let vertSpacing: CGFloat = 10.0 // However much separation you want in between boxes
self.allCalendars.append(allCalendars[i] as EKCalendar)
let checkbox = NSButton.init(checkboxWithTitle: allCalendars[i].title, target: self, action: #selector(self.checkboxDidChange))
checkbox.setButtonType(NSToggleButton)
// Now you can set the frame of the checkbox within it's superview's coordinate system
// This calculates it based on the bounds.height, if you want to use a custom height, substitute that.
checkbox.frame = CGRect(x: margin, y: vertSpacing + (vertSpacing + checkbox.bounds.height)*CGFloat(i), width: checkbox.bounds.width, height: checkbox.bounds.height)
self.checkboxArray.append(checkbox as NSButton)
self.addSubview(checkbox)
}
Basically what that y-value vertSpacing + (vertSpacing + checkbox.bounds.height)*CGFloat(i) does is it starts the very first checkbox off with a y-pos of whatever you set your vertical spacing value to be, and then for every checkbox after, it will set it one vertical space value below the previous checkbox. This solution will assume that each checkbox is of the same height. You can set a custom height if you don't want to use the bounds.height value.

Unable to increase height and width of UIView when using CGAffineTransformMakeRotation

I am rotating UIView by using UISLider Values. If slider values either get increase or decrease, according to that rotation happen. My code is working fine.
Rotation Action:
#IBAction func sliderToolAction(sender: UISlider) {
if sender.tag == 1
{
let radians = CGFloat(sender.value) * 22 / (7 * 180)
rotatingView.transform = CGAffineTransformMakeRotation(radians)
}
}
Output:
Rotating Frame: (100, 20, 103.9990, 127.8889)
After rotating that UIView, I tried to increase width and height of that view, its not working.
Resize Action
#IBAction func sliderToolAction(sender: UISlider) {
if sender.tag == 2
{
rotatingView.frame.size.width = CGFloat(sender.value) * viewWidth
rotatingView.frame.size.height = CGFloat(sender.value) * viewHeight
}
}
Output:
Rotating Frame: (80, 40, 207.8888, 255.6667) //ATTEMPT 1
Rotating Frame: (50, 60, 207.8888, 255.6667) //ATTEMPT 2
Rotating Frame: (20, 90, 207.8888, 255.6667) //ATTEMPT 3
Rotating Frame: (-10, 120, 207.8888, 255.6667) //ATTEMPT 4
I am increasing width and height alone, but UIView position is moving out of screen. Kindly guide me on this.
Straight from Apple's documentation:
Warning
If the transform property is not the identity transform, the value of
this property is undefined and therefore should be ignored.
and:
Changes to this property can be animated. However, if the transform
property contains a non-identity transform, the value of the frame
property is undefined and should not be modified. In that case, you
can reposition the view using the center property and adjust the size
using the bounds property instead.
Instead of changing the frame size, change the bounds size.

NSTextView not resizing properly after setFrameSize

In an NSTextView subclass I have created, I want to resize the height of the view to the height of the text within it. To execute this, I used apple's recommended procedure of counting lines within a text view:
private func countln() -> Int {
var nlines: Int
var index: Int
var range = NSRange()
let nGlyphs = lManager.numberOfGlyphs
for (nlines = 0, index = 0; index < nGlyphs; nlines++) {
lManager.lineFragmentRectForGlyphAtIndex(index, effectiveRange: &range)
index = NSMaxRange(range);
}
return nlines
}
This method works as expected and returns the correct number of lines in the text view. The issue lies in the resizing of the view, which I inserted into the delegate method that is called on text change:
func textDidChange(notification: NSNotification) {
let newHeight = CGFloat(28 * countln())
let ogHeight = self.frame.height
self.setFrameSize(NSSize(width: self.frame.width, height: newHeight))
self.setFrameOrigin(NSPoint(x: self.frame.origin.x, y: (self.frame.origin.y - self.frame.height) + ogHeight))
Swift.print(frame.height)
}
The setFrameSize variable function resizes the height of the view based not the number of lines in the view (multiplied by a constant that is more-or-less the height of each line of text). Everything works perfectly until immediately after the change of height is made, when the text view's height changes to an unanticipated incorrect height. I presume there is an issue with the frequent redrawing of the view in relation to the way I am resizing it. Any help on how to solve this issue of incorrect height resizing is greatly appreciated.
Thanks in advance.