Possible to get view size from within a class? (Swift) - ios

If a subview of class MyClass is added to another view:
secondView = MyClass()
mainView.addSubview(secondView)
is it possible to get the subview's dimensions from within the class?
secondView = MyClass()
mainView.addSubview(secondView)
class MyClass: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// do whatever the view will need to show
// *** can I get the view's dimensions here? <----------------------***
// e.g., draw a diagonal line
// from top left corner to bottom right corner
// self.bounds and self.frame return (0, 0, 0, 0) here
}
}
Or do they need to be passed in somehow from the view creating it (mainView)?

You cannot get a view's size reliably within init. A view's size can change. Therefore you should either do any drawing you need in drawrect, or if you are using CALAyer, resize your layer in layoutSubviews where you will have the bounds of your view and you can then use that to set the frame of your sublayer or subviews.
https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews
https://developer.apple.com/documentation/uikit/uiview/1622529-drawrect
Here is a playground with a simple line example.
import UIKit
import Combine
import PlaygroundSupport
final class XView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
UIColor.red.setStroke()
let path = UIBezierPath()
path.lineWidth = 10
path.move(to: rect.origin)
path.addLine(to: .init(x: rect.maxX, y: rect.maxY))
path.stroke()
}
}
let v = XView()
v.frame = .init(origin: .zero, size: .init(width: 300, height: 300))
PlaygroundPage.current.liveView = XView()
If you want the fancy version see my GitHub project here:
https://github.com/joshuajhomann/pathmorpher/tree/master/final

Related

How to get Width or Height of a UIView when I'm using "Equal width" constrain

I need help to solve this problem: I have a UITextFiled and I'm trying to apply a border at the bottom using this code:
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.addSublayer(border)
}
The problem is that the result is not correct, the border goes outside the textfield because in the text Field I'm using the "Equal width constrain" and the Width at design time is not the same Width at "Didload()" time. There is a way to get the width to the textField after "Equal width constrain" correction?
A much better approach is to
subclass UITextField
create the "underline border" layer on initialization
change the frame of that layer in layoutSubviews()
Example:
#IBDesignable
class UnderlinedTextField: UITextField {
let underlineLayer: CALayer = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(underlineLayer)
underlineLayer.backgroundColor = UIColor.black.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
underlineLayer.frame = CGRect(x: 0, y: bounds.height - 2.0, width: bounds.width, height: 2)
}
}
Result (I gave the text field a background color of .cyan to make it easy to see):
It automatically resizes the "underline" when the field size changes - such as on device rotation:
Note that, by making it #IBDesignable, you can also see the underline layer during design-time.
This example uses a default color of black for the "underline" but you can change it via code just like any other property change, e.g.:
testField.underlineLayer.backgroundColor = UIColor.red.cgColor
Override bounds variable and call your border drawing in didSet. Your layer would be updated every time view changes bounds.
var border = CALayer()
init(frame: CGRect) {
super.init(farme: frame)
self.layer.addSublayer(border)
}
override var bounds: CGRect {
didSet {
addBottomBorderWithColor(color: .black, width: 2)
}
}
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.setNeedsLayout()
}
I found a possible solution by myself (not the perfect one).
Because the Constrains are probably applied after DidLoad() and after viewDidLayoutSubviews(), I called the function to add the border inside the function viewDidAppear(). Now it works even if the new borders are shown with a small delay.
The best way is sub-class a UITextFiled as described here.
Custom class that can be applied to every UITextField - Swift
In this case there the object is created correctly

How to get UIImageView's subview (a UIView) to cover it entirely?

I have a UIImageView placed on the storyboard, and am trying to programmatically add a UIView as a subview to it, and have that UIView match the parent UIImageView's size and position exactly so that it covers it.
The UIView is a custom class of UIView thats drawing a CAShapeLayer that fills its frame, if that matters at all.
In addition to the following code, I've also tried "redOverlay.center = parentImage.center" without success.
In my viewdidload() i have:
let redOverlay = RedOverlay(frame: CGRect(x: 0.0, y: 0.0, width: parentImage.bounds.width, height: parentImage.bounds.height))
parentImage.addSubview(redOverlay)
And here is my RedOverlay subclass of UIView if that makes a difference:
import UIKit
extension CGFloat {
func toRadians() -> CGFloat {
return self * CGFloat.pi / 180.0
}
}
class RedOverlay: UIView {
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray.withAlphaComponent(0.5)
pie()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func pie() {
path = UIBezierPath()
path.move(to: CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2))
path.addArc(withCenter: CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2),
radius: self.bounds.size.width/2,
startAngle: CGFloat(215).toRadians(),
endAngle: CGFloat(90).toRadians(),
clockwise: true)
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.opacity = 0.5
self.layer.addSublayer(shapeLayer)
}
}
The result I get actually works perfectly on iPhone 6, but anything larger like 6s or ipads, and the child UIView is actually noticeably smaller than the UIImageView. In the iPad, the UIView sits well to the left, while in the 6s it is only a bit left.
Basically, it seems like the UIView being generated isnt as wide as the image, even though parentImage.bounds.width was used to determine the UIView's width. I do have "Clip to Bounds" and Aspect Fit set in IB so I don't know why the image would be bigger than it's bounds if thats possible at all.
Whats the cleanest way to get this subview to match the parent view in all device sizes?
EDIT: The solution I found was to call my function pie() (that draws the CAShapeLayer), by overriding layoutSubviews and calling pie() there so that my shapeLayer is updated whenever the view changes. I removed .addSublayer(shapeLayer) from pie() and now I call it in viewdidload in case layoutsubviews being called repeatedly might cause many layers to be created.
override func layoutSubviews() {
super.layoutSubviews()
pie()
}

CollectionViewCell Draw Rect

I'm trying to draw a simple outlined circle inside my collection view cells. For some reason, only the first cell is being draw, the rest are not showing.
class UserCell: UICollectionViewCell {
override func draw(_ rect: CGRect) {
let center = CGPoint(x: self.center.x - 1, y: 41)
let circularPath = UIBezierPath()
circularPath.addArc(withCenter: center, radius: 36, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: true)
UIColor.red.setStroke()
circularPath.lineWidth = 2
circularPath.stroke()
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
What am I missing here?
Collection view cells are configured for display using UICollectionViewDataSource method cellForItemAt(). The cells are reused and won't automatically redraw for each 'new' cell. Instead of overriding draw(rect), add subviews to the cell and configure the subviews in cellForItemAt().
You might want to define your center point differently. The center point is specified in points in the coordinate system of its superview. Either try convert the cell's center point from its superview's coordinate system or use the bounds of the cell instead and adjust the x and y values accordingly.
let center = self.convert(self.center, from: self.superview)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
Created a new class conforming to UIView(), added the bezierPath info inside its draw function. Then subclassed this class inside the collectionView cell. Works as expected.

UIView / UIControl ignoring frame rect. Always fullscreen

I am trying to build custom components in Swift 2.2, and obviously missing something essential. My views ignore the frame rect.
In the containing view, I add the view programmatically like this:
let arrowControl = ArrowControl(frame: CGRect(x: 100 , y: 100, width: 300, height: 300))
self.view.addSubview(arrowControl)
In fact, even if I programmatically add a UIView or UIControl, without subclassing it - then the view ignores the frame rect, and occupies the entire screen.
Here are the essential parts of my custom view:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
renderComponent(frame)
}
EDIT>>>>
If I change my example, and remove renderComponent, I still get the same problem.
override func layoutSubviews() {
super.layoutSubviews()
self.clipsToBounds = true
backgroundColor = UIColor.yellowColor()
// renderComponent(self.frame)
}
Or even doing the following ignores the frame rect, and gives me a full screen block of colour:
let test = UIView(frame: CGRect(x: 100 , y: 100, width: 300, height: 300))
test.backgroundColor = UIColor.redColor()
self.view.addSubview(test)
Doh! I worked it out. It turns out it was my own fault, as I'd implemented the following in the containing view:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
view.frame = UIScreen.mainScreen().bounds
}
}

Text bottom + center UILabel iOS Swift

I have created an iOS application using Swift. Now I have one issue I want to make my text center + bottom in one label and center + top in another label. Is it possible to do?
I tried this
//Here learnItem is my label.
let constraintSize: CGSize = CGSizeMake(learnItem.frame.size.width, CGFloat(MAXFLOAT))
let textRect: CGRect = learnItem.text!.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: learnItem.font], context: nil)
learnItem.drawRect(textRect);
It doesn't affect anything. So maybe it is a wromg code. Also I saw
learnItem.textAlignment = .Center
There is option for center, justified, left, natural, right. But that is not what I want.
Please some one help me how to make my text center + bottom and center + top?
In order to do this you should override drawTextInRect method.
// MARK: - BottomAlignedLabel
#IBDesignable class BottomAlignedLabel: UILabel {
// MARK: Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawText(in rect: CGRect) {
guard text != nil else {
return super.drawText(in: rect)
}
let height = self.sizeThatFits(rect.size).height
let y = rect.origin.y + rect.height - height
super.drawText(in: CGRect(x: 0, y: y, width: rect.width, height: height))
}
}
For top alignment y should be 0.
If you're using storyboard the easiest way to use this code is just change class for your label in storyboard.

Resources