Related
I have a scenario where I'm needed to create an UIView completely programatically. This view is displayed as an overlay over a video feed and it contains 2 UILabels as subviews.
These labels can have varying widths and heights so I don't want to create a fixed frame for the UIView or the labels.
I've done this with programatic constrains without issue, but the problem is that the views are glitching when moving the phone and the framework that I'm using that does the video feed recommends not to use autolayout because of this exact problem.
My current code:
I create the view
func ar(_ arViewController: ARViewController, viewForAnnotation: ARAnnotation) -> ARAnnotationView {
let annotationView = AnnotationView() //*this is the view, inherits from UIView
annotationView.annotation = viewForAnnotation
annotationView.delegate = self
return annotationView
}
And the actual view code
class AnnotationView: ARAnnotationView {
var titleLabel: UILabel?
var distanceLabel: UILabel!
var delegate: AnnotationViewDelegate?
override var annotation: ARAnnotation? {
didSet {
loadUI()
}
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
setupUI()
}
private func setupUI() {
layer.cornerRadius = 5
layer.masksToBounds = true
backgroundColor = .transparentWhite
}
private func loadUI() {
titleLabel?.removeFromSuperview()
distanceLabel?.removeFromSuperview()
let label = UILabel()
label.font = UIFont(name: "AvenirNext-Medium", size: 16.0) ?? UIFont.systemFont(ofSize: 14)
label.numberOfLines = 2
label.textColor = UIColor.white
self.titleLabel = label
distanceLabel = UILabel()
distanceLabel.textColor = .cbPPGreen
distanceLabel.font = UIFont(name: "AvenirNext-Medium", size: 14.0) ?? UIFont.systemFont(ofSize: 12)
distanceLabel.textAlignment = .center
self.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
distanceLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(label)
self.addSubview(distanceLabel)
if self.constraints.isEmpty {
NSLayoutConstraint.activate([
NSLayoutConstraint(item: label, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 5),
NSLayoutConstraint(item: label, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: -5),
NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: distanceLabel, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: distanceLabel, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: distanceLabel, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0),
NSLayoutConstraint(item: distanceLabel, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: distanceLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 15),
])
}
if let annotation = annotation as? BikeStationAR {
titleLabel?.text = annotation.address
distanceLabel?.text = String(format: "%.2f km", annotation.distanceFromUser / 1000)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
delegate?.didTouch(annotationView: self)
}
}
So my problem is now: how can I achieve the same behavior, without using autolayout?
Edit: here is a video of the behavior https://streamable.com/s/4zusq/dvbgnb
Edit 2: The solution is based on Mahgol Fa's answer and using a stack view or vertical positioning in order not to set the frame for the individual labels. So the final implementation is
private func loadUI() {
titleLabel?.removeFromSuperview()
distanceLabel?.removeFromSuperview()
let label = UILabel()
label.font = titleViewFont
label.numberOfLines = 0
label.textColor = UIColor.white
label.textAlignment = .center
self.titleLabel = label
distanceLabel = UILabel()
distanceLabel.textColor = .cbPPGreen
distanceLabel.font = subtitleViewFont
distanceLabel.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
distanceLabel.translatesAutoresizingMaskIntoConstraints = false
if let annotation = annotation as? BikeStationAR {
let title = annotation.address
let subtitle = String(format: "%.2f km", annotation.distanceFromUser / 1000)
let fontAttributeTitle = [NSAttributedString.Key.font: titleViewFont]
let titleSize = title.size(withAttributes: fontAttributeTitle)
let fontAttributeSubtitle = [NSAttributedString.Key.font: subtitleViewFont]
let subtitleSize = annotation.address.size(withAttributes: fontAttributeSubtitle)
titleLabel?.text = title
distanceLabel?.text = subtitle
let stackView = UIStackView(frame: CGRect(x: 0, y: 0, width: titleSize.width + 5, height: titleSize.height + subtitleSize.height + 5))
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.alignment = .fill
stackView.addArrangedSubview(titleLabel ?? UIView())
stackView.addArrangedSubview(distanceLabel)
frame = stackView.bounds
addSubview(stackView)
}
}
This way you can get the width of your lablels texts:
let fontAttributes1= [NSAttributedStringKey.font: your declared font in your code]
let size1 = (“your string” as String).size(withAttributes: fontAttributes1)
let fontAttributes2= [NSAttributedStringKey.font: your declared font in your code]
let size2 = (“your string” as String).size(withAttributes: fontAttributes2)
Then :
YourView.frame = CGRect( width: size1.width + size2.width + some spacing , height : ....)
I'm trying to use inputAccessoryViewController in my app, but faced a problem with changing height of accessory view. I tried to change frame/bounds of the view, I also tried to handle height of the accessory view using constraints. But nothing worked well.
InputViewController code:
import UIKit
import RxSwift
import RxCocoa
class InputViewController: UIInputViewController {
private var separatorView: UIView?
private var answerTextView: ConstrainedTextView?
private var closeButton: UIButton?
private var tipLabel: UILabel?
// private var generalHeightConstraint: NSLayoutConstraint?
private var separatorHeightConstraint: NSLayoutConstraint?
private var answerTextViewBottomConstraint: NSLayoutConstraint?
private let junk = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
private func configureView() {
// view.autoresizingMask = .flexibleHeight
view.backgroundColor = UIColor.white
view.frame = CGRect(x: 0, y: 0, width: view.superview?.bounds.width ?? 100, height: 70)
// view.translatesAutoresizingMaskIntoConstraints = false
// generalHeightConstraint = AutoLayoutSetAttribute(view, .height, 70)
// Separator
separatorView = UIView()
separatorView?.backgroundColor = UIColor.gray
separatorView?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(separatorView!)
AutoLayoutEqualizeSuper(separatorView, .left, 0)
AutoLayoutEqualizeSuper(separatorView, .right, 0)
AutoLayoutEqualizeSuper(separatorView, .top, 0)
separatorHeightConstraint = AutoLayoutSetAttribute(separatorView, .height, 1)
// Close Button
closeButton = UIButton(type: .system)
closeButton?.setTitle("Hide", for: .normal)
closeButton?.titleLabel?.font = UIFont.systemFont(ofSize: 17)
closeButton?.translatesAutoresizingMaskIntoConstraints = false
closeButton?.addTarget(self, action: #selector(dismissKeyboard), for: .touchUpInside)
view.addSubview(closeButton!)
AutoLayoutSetAttribute(closeButton, .width, 70)
AutoLayoutEqualizeSuper(closeButton, .right, -5)
view.addConstraint(NSLayoutConstraint(item: closeButton!, attribute: .top, relatedBy: .equal, toItem: separatorView, attribute: .bottom, multiplier: 1, constant: 0))
// Tip Label
tipLabel = UILabel()
tipLabel?.textColor = UIColor.darkGray
tipLabel?.text = "Input answer:"
tipLabel?.font = UIFont.systemFont(ofSize: 17)
tipLabel?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tipLabel!)
AutoLayoutEqualizeSuper(tipLabel, .left, 5)
AutoLayoutEqualize(tipLabel, separatorView, .top, 0)
view.addConstraint(NSLayoutConstraint(item: tipLabel!, attribute: .right, relatedBy: .equal, toItem: closeButton, attribute: .left, multiplier: 1, constant: 0))
// Text View
answerTextView = ConstrainedTextView()
answerTextView?.backgroundColor = UIColor.white
answerTextView?.delegate = self
answerTextView?.scrollsToTop = false
answerTextView?.showsVerticalScrollIndicator = false
answerTextView?.font = REG_FONT(15)
answerTextView?.maxLines = 5
answerTextView?.translatesAutoresizingMaskIntoConstraints = false
answerTextView?.layer.masksToBounds = true
answerTextView?.layer.cornerRadius = 7
answerTextView?.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.7).cgColor
answerTextView?.layer.borderWidth = 1
view.addSubview(answerTextView!)
AutoLayoutEqualizeSuper(answerTextView, .left, 5)
AutoLayoutEqualizeSuper(answerTextView, .right, -5)
answerTextViewBottomConstraint = AutoLayoutEqualizeSuper(answerTextView, .bottom, -5)
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: tipLabel, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: closeButton, attribute: .bottom, multiplier: 1, constant: 0))
answerTextView?
.rx
.observe(CGRect.self, "bounds")
.distinctUntilChanged {
$0?.size.height == $1?.size.height
}
.subscribe { [unowned self] newBounds in
if var newHeight = newBounds.element??.size.height,
let separatorHeight = self.separatorHeightConstraint?.constant,
let buttonHeight = self.closeButton?.intrinsicContentSize.height,
let bottomSpace = self.answerTextViewBottomConstraint?.constant {
newHeight = newHeight == 0 ? 30 : newHeight
let generalHeight = newHeight + separatorHeight + buttonHeight + abs(bottomSpace)
self.view.frame = CGRect(x: 0, y: 0, width: self.view.superview?.bounds.width ?? 100, height: generalHeight)
// self.generalHeightConstraint?.constant = generalHeight
// UIView.animate(withDuration: 0.2) {
// self.view.setNeedsLayout()
// self.view.layoutIfNeeded()
// }
}
}
.addDisposableTo(junk)
}
}
// MARK: - UITextViewDelegate Protocol Conformance
extension InputViewController: UITextViewDelegate {
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
textView.inputAccessoryView = view
return true
}
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
textView.inputAccessoryView = nil
return true
}
}
View Controller where input accessory VC is used:
import UIKit
class TestViewController: UIViewController {
override var inputAccessoryViewController: UIInputViewController? {
return SDAnswerInputViewController()
}
override var canBecomeFirstResponder: Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Can you explain how shall I correctly modify height of input accessory view overriding inputAccessoryViewController?
The problem was in these two lines:
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: tipLabel, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: answerTextView!, attribute: .top, relatedBy: .equal, toItem: closeButton, attribute: .bottom, multiplier: 1, constant: 0))
The answerTextView couldn't modify it's height because of constraints at the bottom and the top.
I'm working with a iMessage application and have programmatically added a view. However I can't seem to work out the correct constraints for making it the correct size at all times. For example, the view moves down a few hundred px if I leave the extension for another and come back to it. I think this has something to do with the .isActive. My goal is to make the view automatically resize to always be the right size or take up the full available height and width.
func createBrowser() {
let controller = MSStickerBrowserViewController(stickerSize: .small)
addChildViewController(controller)
view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
controller.stickerBrowserView.backgroundColor = UIColor.blue
controller.stickerBrowserView.dataSource = self
view.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
view.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
view.centerXAnchor.constraint(equalTo: controller.view.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: controller.view.centerYAnchor).isActive = true
}
Screenshot: https://d17oy1vhnax1f7.cloudfront.net/items/1F2B0s3v0s1k3E2L0Z07/Screen%20Shot%202016-09-19%20at%2011.42.51%20AM.png
to better explain things I've put together the following. This demonstrates two methods of fixing the layout for subviews. When using constraints, I prefer to create the constraints as an array and activate them all in one go, as you will see in the code for createredSquareWithConstraints. A constraint is simply a linear equation relating the features of one view to that of another. In "pseudocode", for example, the first constraint in my array could be written:
"Set the leading margin of the subview equal to 1 times the leading margin of the container view plus a constant of 0."
(This is why I was getting confused earlier as it looked to me as though you were setting the containing view's constraints based on the characteristics of one of its subviews.)
While it remains perfectly valid to use layout constraints, I think the preferred methodology these days is to override the viewWillTransitionToSize() delegate method, which simply asks you to specify, given a size for the containing view, what the frame of a view controller's subviews should be. As such, I've included an implementation of this too, creating a yellow square with an initial frame that is then modified whenever viewWillTransitionToSize is called. I personally find this a lot less fiddly that using layout constraints.
If you lay around with the buttons and rotate the screen you should see that either method achieves the same thing. [NB I have labelled one square as constrained and one as unconstrained, but in reality they are of course both constrained, just in different ways. I would add that this is clearly not how you would do things in practice - you should choose one methodology and stick to it otherwise your code will be all over the place!].
Hope that helps!
import UIKit
class ViewController: UIViewController {
var constrainedredSquare : UIView!
var unconstrainedRedSquare : UIView!
var methodOneButton : UIButton!
var methodTwoButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = UIColor.blue
func getButton(name: String) -> UIButton {
let button : UIButton = UIButton()
button.backgroundColor = UIColor.white
button.layer.cornerRadius = 3
button.clipsToBounds = true
button.setTitle(name, for: UIControlState.normal)
button.setTitleColor(UIColor.black, for: UIControlState.normal)
return button
}
self.methodOneButton = getButton(name: "Red - Constraints")
self.methodTwoButton = getButton(name: "Yellow - viewWillTransitionToSize")
self.methodOneButton.addTarget(self, action: #selector(self.createRedSquareWithConstraints), for: .touchUpInside)
self.methodTwoButton.addTarget(self, action: #selector(self.createYellowSquareWithoutConstraints), for: .touchUpInside)
self.methodOneButton.frame = CGRect(origin: CGPoint(x: 200, y: 100), size: CGSize(width: 300, height: 300))
self.methodTwoButton.frame = CGRect(origin: CGPoint(x: self.view.frame.width - 500, y: 100), size: CGSize(width: 300, height: 300))
self.view.addSubview(self.methodOneButton)
self.view.addSubview(self.methodTwoButton)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if let _ = self.unconstrainedRedSquare {
self.unconstrainedRedSquare.frame = CGRect(origin: CGPoint.zero, size: size)
}
self.methodOneButton.frame = CGRect(origin: CGPoint(x: 200, y: 100), size: CGSize(width: 300, height: 300))
self.methodTwoButton.frame = CGRect(origin: CGPoint(x: size.width - 500, y: 100), size: CGSize(width: 300, height: 300))
}
func createYellowSquareWithoutConstraints() {
if let _ = self.unconstrainedRedSquare {
self.unconstrainedRedSquare.removeFromSuperview()
}
else
{
if let _ = constrainedredSquare {
self.constrainedredSquare.removeFromSuperview()
}
self.unconstrainedRedSquare = UIView()
self.unconstrainedRedSquare.backgroundColor = UIColor.yellow
self.unconstrainedRedSquare.frame = CGRect(origin: CGPoint.zero, size: self.view.frame.size)
self.view.addSubview(self.unconstrainedRedSquare)
self.view.bringSubview(toFront: self.methodOneButton)
self.view.bringSubview(toFront: self.methodTwoButton)
}
}
func createRedSquareWithConstraints() {
if let _ = self.constrainedredSquare {
self.constrainedredSquare.removeFromSuperview()
}
else
{
if let _ = self.unconstrainedRedSquare {
self.unconstrainedRedSquare.removeFromSuperview()
}
let redSquare : UIView = UIView()
redSquare.backgroundColor = UIColor.red
self.view.addSubview(redSquare)
self.view.bringSubview(toFront: self.methodOneButton)
self.view.bringSubview(toFront: self.methodTwoButton)
let rsConstraints : [NSLayoutConstraint] = [NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.leading, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.trailing, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.bottom, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: redSquare, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.height, multiplier: 1.0, constant: 0)]
redSquare.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(rsConstraints)
}
}
}
You can use my extension to UIView. It allows to add extra padding on any side (only if you want to):
public extension UIView {
typealias ConstraintsTupleStretched = (top:NSLayoutConstraint, bottom:NSLayoutConstraint, leading:NSLayoutConstraint, trailing:NSLayoutConstraint)
func addSubviewStretched(subview:UIView?, insets: UIEdgeInsets = UIEdgeInsets() ) -> ConstraintsTupleStretched? {
guard let subview = subview else {
return nil
}
subview.translatesAutoresizingMaskIntoConstraints = false
addSubview(subview)
let constraintLeading = NSLayoutConstraint(item: subview,
attribute: .Left,
relatedBy: .Equal,
toItem: self,
attribute: .Left,
multiplier: 1,
constant: insets.left)
addConstraint(constraintLeading)
let constraintTrailing = NSLayoutConstraint(item: self,
attribute: .Right,
relatedBy: .Equal,
toItem: subview,
attribute: .Right,
multiplier: 1,
constant: insets.right)
addConstraint(constraintTrailing)
let constraintTop = NSLayoutConstraint(item: subview,
attribute: .Top,
relatedBy: .Equal,
toItem: self,
attribute: .Top,
multiplier: 1,
constant: insets.top)
addConstraint(constraintTop)
let constraintBottom = NSLayoutConstraint(item: self,
attribute: .Bottom,
relatedBy: .Equal,
toItem: subview,
attribute: .Bottom,
multiplier: 1,
constant: insets.bottom)
addConstraint(constraintBottom)
return (constraintTop, constraintBottom, constraintLeading, constraintTrailing)
}
}
Usage:
view.addSubviewStretched(tableView)
let BorderedBackgroundInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
view?.addSubviewStretched(calendar.view, insets: BorderedBackgroundInset)
I would like to achieve the same result as this :
I already saw this : Draw line in UILabel before and after text , but I would like to know if there's a way to do this with only one UILabel ?
This is custom view as you requested
import UIKit
class CustomizedUILabel: UIView
{
let label = UILabel()
var lineInsideOffset: CGFloat = 20
var lineOutsideOffset: CGFloat = 4
var lineHeight: CGFloat = 1
var lineColor = UIColor.grayColor()
//MARK: - init
override init(frame: CGRect)
{
super.init(frame: frame)
initLabel()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
initLabel()
}
convenience init() {self.init(frame: CGRect.zero)}
func initLabel()
{
label.textAlignment = .Center
label.translatesAutoresizingMaskIntoConstraints = false
let top = NSLayoutConstraint(item: self, attribute: .Top, relatedBy: .Equal, toItem: label, attribute: .Top, multiplier: 1, constant: 0)
let bot = NSLayoutConstraint(item: self, attribute: .Bottom, relatedBy: .Equal, toItem: label, attribute: .Bottom, multiplier: 1, constant: 0)
let lead = NSLayoutConstraint(item: self, attribute: .Leading, relatedBy: .LessThanOrEqual, toItem: label, attribute: .Leading, multiplier: 1, constant: 0)
let trail = NSLayoutConstraint(item: self, attribute: .Trailing, relatedBy: .GreaterThanOrEqual, toItem: label, attribute: .Trailing, multiplier: 1, constant: 0)
let centerX = NSLayoutConstraint(item: self, attribute: .CenterX, relatedBy: .Equal, toItem: label, attribute: .CenterX, multiplier: 1, constant: 0)
addSubview(label)
addConstraints([top, bot, lead, trail, centerX])
//... if the opaque property of your view is set to YES, your drawRect: method must totally fill the specified rectangle with opaque content.
//http://stackoverflow.com/questions/11318987/black-background-when-overriding-drawrect-in-uiscrollview
opaque = false
}
//MARK: - drawing
override func drawRect(rect: CGRect)
{
let lineWidth = label.frame.minX - rect.minX - lineInsideOffset - lineOutsideOffset
if lineWidth <= 0 {return}
let lineLeft = UIBezierPath(rect: CGRectMake(rect.minX + lineOutsideOffset, rect.midY, lineWidth, 1))
let lineRight = UIBezierPath(rect: CGRectMake(label.frame.maxX + lineInsideOffset, rect.midY, lineWidth, 1))
lineLeft.lineWidth = lineHeight
lineColor.set()
lineLeft.stroke()
lineRight.lineWidth = lineHeight
lineColor.set()
lineRight.stroke()
}
}
Usage in code: in usual case you should provide top, leading and trailing/width constraints and let CustomizedUILabel to determine height by its internal UILabel. Lets show our label in debug mode in some view controller's viewDidLoad method:
override func viewDidLoad()
{
super.viewDidLoad()
let customizedLabel = CustomizedUILabel()
customizedLabel.label.text = "RATE YOUR RIDE"
customizedLabel.label.textColor = UIColor.grayColor()
customizedLabel.label.font = customizedLabel.label.font.fontWithSize(25)
customizedLabel.backgroundColor = UIColor.redColor()
view.addSubview(customizedLabel)
customizedLabel.translatesAutoresizingMaskIntoConstraints = false
let top = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: customizedLabel, attribute: .Top, multiplier: 1, constant: -100)
let trail = NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: customizedLabel, attribute: .Trailing, multiplier: 1, constant: 0)
let lead = NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: customizedLabel, attribute: .Leading, multiplier: 1, constant: 0)
view.addConstraints([top, trail, lead])
}
Usage in xib/storyboard: as soon as you implemented init with coder initializer you can use this view in xib/storyboard. You need add UIView element to your superview, assign Class for this element to CustomizedUILabel, perform constraints and make outlet. Then you can use it with pretty same way:
#IBOutlet weak var customizedLabel: CustomizedUILabel!
override func viewDidLoad()
{
super.viewDidLoad()
customizedLabel.label.text = "RATE YOUR RIDE"
customizedLabel.label.textColor = UIColor.grayColor()
customizedLabel.label.font = customizedLabel.label.font.fontWithSize(25)
customizedLabel.backgroundColor = UIColor.redColor()
}
UILabel * label = [UILabel new];
label.frame = CGRectMake(0,0,0,20);
label.text = #"Rate Your Ride";
[self.view addSubview:label];
[label sizeToFit];
float w = self.view.frame.size.width;
float lw = label.frame.size.width;
float offset = (w-lw)/2;
float padding = 20.0f;
//center the label
[label setFrame:CGRectMake(offset, 0, lw, 20)];
//make the borders
UIImageView * left = [UIImageView new];
left.frame = CGRectMake(0, 9, offset-padding, 2);
left.backgroundColor = [UIColor blackColor];
[self.view addSubview:left];
UIImageView * right = [UIImageView new];
right.frame = CGRectMake(w-offset+padding, 9, offset-padding, 2);
right.backgroundColor = [UIColor blackColor];
[self.view right];
OR
make the label background the same colour as its parent and put a single line behind it.
I'm experiencing a weird bug with the appearance of my inputAccessoryView. While in the middle of a transition, it appears like so:
After the transition, it appears as it should:
I override the property like so:
override var inputAccessoryView: UIView! {
get {
if composeView == nil {
composeView = CommentComposeView(frame: CGRectMake(0, 0, 0, MinimumToolbarHeight - 0.5))
self.setupSignals()
}
return composeView
}
}
I'm wondering if anyone can point out any obvious flaw in what I'm doing or provide some more information on how to ensure my view appears as it should, before, during, and after transitions.
Thanks!
EDIT
Here's my CommentComposeView:
import UIKit
class CommentComposeView: UIToolbar {
var textView: SAMTextView!
var sendButton: UIButton!
private var didSetConstraints: Bool = false
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
private func initialize() {
textView = SAMTextView(frame: CGRectZero)
sendButton = UIButton.buttonWithType(.System) as UIButton
self.barStyle = .Black
self.translucent = true
textView.backgroundColor = UIColor.presentOffWhite()
textView.font = UIFont.presentLightMedium()
textView.layer.borderWidth = 0.5
textView.layer.cornerRadius = 5
textView.placeholder = "Comment"
textView.scrollsToTop = false
textView.textContainerInset = UIEdgeInsetsMake(4, 3, 3, 3)
textView.keyboardAppearance = .Dark
textView.keyboardType = .Twitter
self.addSubview(textView)
sendButton = UIButton.buttonWithType(.System) as UIButton
sendButton.enabled = false
sendButton.titleLabel!.font = UIFont.presentBoldLarge()
sendButton.setTitle("Send", forState: .Normal)
sendButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
sendButton.setTitleColor(UIColor.presentCyan(), forState: .Highlighted)
sendButton.setTitleColor(UIColor.presentLightGray(), forState: .Disabled)
sendButton.contentEdgeInsets = UIEdgeInsetsMake(6, 6, 6, 6)
self.addSubview(sendButton)
RAC(self.sendButton, "enabled") <~ self.textView.rac_textSignal()
.map { text in
return (text as NSString).length > 0
}
textView.setTranslatesAutoresizingMaskIntoConstraints(false)
sendButton.setTranslatesAutoresizingMaskIntoConstraints(false)
}
override func updateConstraints() {
super.updateConstraints()
if !didSetConstraints {
// TODO: Replace raw constraints with a friendlier looking DSL
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 8)
)
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 7.5)
)
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Right, relatedBy: .Equal, toItem: sendButton, attribute: .Left, multiplier: 1, constant: -2)
)
self.addConstraint(
NSLayoutConstraint(item: textView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: -8)
)
self.addConstraint(
NSLayoutConstraint(item: sendButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0)
)
self.addConstraint(
NSLayoutConstraint(item: sendButton, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: -4.5)
)
}
}
}
This is iOS8 issue with inputAccessoryView autolayout. Issue is that UIToolbar's subview of clas _UIToolbarBackground is not positioned properly during initial layout. Try to do next things:
Make CommentComposeView subclassing UIView, not UIToolbar, add instance of UIToolbar as subview.
Use autolayout masks (not actual constraints) inside your CommentComposeView
Override -layoutSubviews in your CommentComposeView like this:
- (void)layoutSubviews
{
[super layoutSubviews];
contentToolbar.frame = self.bounds;
sendButton.frame = CGRectMake(0.f, 0.f, 44.f, self.bounds.size.height);
textView.frame = CGRectMake(44.f, 0.f, self.bounds.size.width - 44.f, self.bounds.size.height);
}