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
}
}
Related
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
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
I have a UIButton subclass intended to show a selected state of a button. The selected state simply places a thick black line at the bottom of the button view and when unselected it hides the black line. However, when using this in a UIButton subclass, the black line view is offset. I have tried playing around with insets, but I don't think that is the problem. Here is my subclass:
class TabButton: UIButton {
private var height:CGFloat = 5
private var selectedIndicator:UIView?
override var isSelected: Bool {
didSet {
selectedIndicator?.isHidden = !isSelected
}
}
fileprivate func initializeSelector(_ frame: CGRect) {
selectedIndicator = UIView(frame: CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height))
selectedIndicator?.backgroundColor = UIColor.black
self.addSubview(selectedIndicator!)
}
override func awakeFromNib() {
super.awakeFromNib()
initializeSelector(self.frame)
}
}
The desired button should look like this:
But instead it looks like this:
Can anyone help me understand what is happening here and how to fix it? Thanks!
Try this, in layoutSubviews you get the final frame:
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
The frame of the selectedIndicator is set only once when initializeSelector is called. When the button changes its frame, it does not change the frame of subviews, you need to manually update the frame of selectedIndicator.
To do so, you need to override layoutSubviews() method of UIView.
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator?.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
See this answer to know when layoutSubviews() is called.
Sometimes I need to create same Views (or ImageViews) in Swift.
For example, make same size and image, delete the view, and make it again (and sometimes change the view's image or size).
In this case, of course writing same codes each time to need the view is not good.
So I want to find good way to create same views multiple times.
Now I use this code when I create same ImageView.
*CreateImage.swift
class aImageView: UIImageView {
override init(frame: CGRect) {
super.init(frame: frame)
let aImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 70, height: 50))
aImageView.image = UIImage(named: "item_a01_70x50")
aImageView.tag = 100
aImageView.isUserInteractionEnabled = true
aImageView.backgroundColor = UIColor.lightGray
self.addSubview(aImageView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
}
class bImageView: UIImageView {
override init(frame: CGRect) {
super.init(frame: frame)
let bImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 70, height: 50))
bImageView.image = UIImage(named: "item_b02_70x50")
bImageView.tag = 200
bImageView.isUserInteractionEnabled = true
bImageView.backgroundColor = UIColor.lightGray
self.addSubview(bImageView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
}
*TopView.swift
class TopView: UIView {
var aview: aImageView!
var bview: bImageView!
init(frame: CGRect, stage:Int){
super.init(frame: frame)
aview = aImageView(frame :CGRect(x: 0, y: 0, width: 70, height: 50))
self.addSubview(aview)
bview = bImageView(frame :CGRect(x: 100, y: 100, width: 70, height: 50))
self.addSubview(bview)
//...and sometimes delete them, create them again other time, and create...
}
}
If you know better way, could you tell me?
Thanks.
i think you can make an extension of UIImageView like this:
extension UIImageView {
convenience init(_imageName: String = "",
_frameTulpe: (x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) = (0, 0, 0, 0),
_tag: Int = 0,
_isUserInteractionEnabled: Bool = true,
_backgroundColor: UIColor = UIColor.lightGray)
{
self.init(image: UIImage(named: _imageName))
frame = CGRect(x: _frameTulpe.x, y: _frameTulpe.y, width: _frameTulpe.w, height: _frameTulpe.h)
tag = _tag
isUserInteractionEnabled = _isUserInteractionEnabled
backgroundColor = _backgroundColor
}
}
And then, in your TopView.swift will be:
class TopView: UIView {
var aview: aImageView!
var bview: bImageView!
init(frame: CGRect, stage:Int){
super.init(frame: frame)
aview = UIImageView(_imageName: "item_a01_70x50", _frameTulpe:(0, 0, 70, 50), _tag: 100)
self.addSubview(aview)
bview = UIImageView(_imageName: "item_b02_70x50", _frameTulpe:(100, 100, 70, 50), _tag: 200)
self.addSubview(bview)
//...and sometimes delete them, create them again other time, and create...
}
}
uh hun! You just need merge the aImageView class and bImageView class. Then the variable property can be set after that the object be created, or you can define the other init method with it is contain the different parameter that be used for initializing the variable property.
Define just one class(ex: Utility.swift) which will provide custom Views.
Last but not the least you need to autolayout after adding subview(ex: aImageView).
I am following this technique.
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.