i builded a static tableview with more Rowes than the screen has, so the user has to scroll to see all cell.
Every cell has a textfield with the following class to add a bottom border:
class TextFieldWithBottomBorder: UITextField {
let border = CALayer()
let width = CGFloat(1.0)
func addBottomBorder(color: UIColor){
self.border.borderColor = color.cgColor
self.border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height:self.frame.size.height)
self.border.borderWidth = self.width
self.layer.addSublayer(self.border)
self.layer.masksToBounds = true
}
func changeBorderColor(color: UIColor){
self.border.borderColor = color.cgColor
}
}
And i call the method after receiving some data from the server e. g.
self.firstnameTextField.text = firstNameFromDB
self.firstnameTextField.addBottomBorder(color: .blue)
This works fine for every cell is currently displayed. But the cells which are out of the current view the with is shorter than the textfield.
See this screenshot, for "Vorname", means firstName everything looks good, but for email, password etc. the border is to short.
http://share-your-photo.com/34b5e80253
Looks like the size of the UITextField is being resized after you have called addBottomBorder and so the UIView being used at the line is now not wide enough. It's difficult to say why this would be without seeing more code but there are several methods you could use to overcome it.
1) Switch to a UIView instead of a CALayer and use auto layout to keep the view in the correction position.
2) Override layoutSubviews to update the frame of the bottom line.
The simplest for you is probably option 2 (although I would go option 1) and it would look like this:
override func layoutSubviews() {
super.layoutSubviews()
self.border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height:self.frame.size.height)
}
Now whenever the frame/size of the text field changes the frame/size of the border line CALayer will be updated appropriately.
Use this class for bottom line text field
#IBDesignable class BottomTextField: UITextField {
var lineView = UIView()
#IBInspectable var lineViewBgColor:UIColor = UIColor.gray{
didSet {
if !isFirstResponder {
lineView.backgroundColor = lineViewBgColor
}
}
}
required init?(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)!
setup()
}
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
// MARK:- Private Methods
private func setup() {
lineView.frame = CGRect(x:CGFloat(0), y:self.frame.size.height-2, width:self.frame.size.width, height:CGFloat(1))
lineView.backgroundColor = lineViewBgColor
self.addSubview(lineView)
}
}
I added shadow to my table view but unfortunately when I scroll through the table view the shadow also moves with the table. The code for adding shadow is as follows:
func addShadow(to myView: UIView){
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: uiView.frame.width, height: uiView.frame.height * 1.1))
uiView.layer.shadowColor = UIColor.black.cgColor
uiView.layer.shadowOffset = CGSize(width: 0.2, height: 0)
uiView.layer.shadowOpacity = 0.5
uiView.layer.shadowRadius = 5.0
uiView.layer.masksToBounds = false
uiView.layer.shadowPath = shadowPath.cgPath
}
Can you please explain to me why is this happening and how to make the shadow stick to its designated location?
Thank you in advance.
If you are adding shadow on tableView, it will scroll along with tableview data. To prevent that you have to add UIView first. Add tableview on that view. Add shadow for the UIView you have taken. It will stick to designated location.
This is my solution in Swift 3 with an UIView and a CAGradientLayer inside.
override func viewWillAppear(_ animated: Bool) {
addShadow(myView: myTab)
}
func addShadow(myView: UIView){
let shadowView = UIView()
shadowView.center = CGPoint(x: myView.frame.minX,y:myView.frame.minY - 15)
shadowView.frame.size = CGSize(width: myView.frame.width, height: 15)
let gradient = CAGradientLayer()
gradient.frame.size = shadowView.frame.size
let stopColor = UIColor.clear.cgColor
let startColor = UIColor.black.withAlphaComponent(0.8).cgColor
gradient.colors = [stopColor,startColor]
gradient.locations = [0.0,1.0]
shadowView.layer.addSublayer(gradient)
view.addSubview(shadowView)
}
Basing on the source code below:
#IBOutlet var myUIImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.makingRoundedImageProfileWithRoundedBorder()
}
private func makingRoundedImageProfileWithRoundedBorder() {
// Making a circular image profile.
// self.myUIImageView.layer.cornerRadius = self.myUIImageView.frame.size.width / 2
// Making a rounded image profile.
self.myUIImageView.layer.cornerRadius = 20.0
self.myUIImageView.clipsToBounds = true
// Adding a border to the image profile
self.myUIImageView.layer.borderWidth = 10.0
self.myUIImageView.layer.borderColor = UIColor.whiteColor().CGColor
}
Indeed I am able to render a circular or rounded UIImageView, but the problem is that if we add the border, the image leaks a bit. It's way worse with a circular UIImageView, it leaks whenever the border is bent, so LEAKS EVERYWHERE! You can find a screenshot of the result below:
Any way to fix that in Swift? Any sample code which answers to this question will be highly appreciated.
Note: as far as possible the solution has to be compatible with iOS 7 and 8+.
First Solution
Basing on the #Jesper Schläger suggestion
"If I may suggest a quick and dirty solution:
Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0."
Please find the Swift implementation below:
import UIKit
class ViewController: UIViewController {
#IBOutlet var myUIImageView: UIImageView!
#IBOutlet var myUIViewBackground: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Making a circular UIView: cornerRadius = self.myUIImageView.frame.size.width / 2
// Making a rounded UIView: cornerRadius = 10.0
self.roundingUIView(self.myUIImageView, cornerRadiusParam: 10)
self.roundingUIView(self.myUIViewBackground, cornerRadiusParam: 20)
}
private func roundingUIView(let aView: UIView!, let cornerRadiusParam: CGFloat!) {
aView.clipsToBounds = true
aView.layer.cornerRadius = cornerRadiusParam
}
}
Second Solution
Would be to set a circle mask over a CALayer.
Please find the Objective-C implementation of this second solution below:
CALayer *maskedLayer = [CALayer layer];
[maskedLayer setFrame:CGRectMake(50, 50, 100, 100)];
[maskedLayer setBackgroundColor:[UIColor blackColor].CGColor];
UIBezierPath *maskingPath = [UIBezierPath bezierPath];
[maskingPath addArcWithCenter:maskedLayer.position
radius:40
startAngle:0
endAngle:360
clockwise:TRUE];
CAShapeLayer *maskingLayer = [CAShapeLayer layer];
[maskingLayer setPath:maskingPath.CGPath];
[maskedLayer setMask:maskingLayer];
[self.view.layer addSublayer:maskedLayer];
If you comment out from line UIBezierPath *maskingPath = [UIBezierPath bezierPath]; through [maskedLayer setMask:maskingLayer]; you will see that the layer is a square. However when these lines are not commented the layer is a circle.
Note: I neither tested this second solution nor provided the Swift implementation, so feel free to test it and let me know if it works or not through the comment section below. Also feel free to edit this post adding the Swift implementation of this second solution.
If I may suggest a quick and dirty solution:
Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0.
I worked on improving the code but it keeps crashing. I'll work on it, but I appear to have got a (rough) version working:
Edit Updated with a slightly nicer version. I don't like the init:coder method but maybe that can factored out/improved
class RoundedImageView: UIView {
var image: UIImage? {
didSet {
if let image = image {
self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
}
}
var cornerRadius: CGFloat?
private class func frameForImage(image: UIImage) -> CGRect {
return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
override func drawRect(rect: CGRect) {
if let image = self.image {
image.drawInRect(rect)
let cornerRadius = self.cornerRadius ?? rect.size.width/10
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
UIColor.whiteColor().setStroke()
path.lineWidth = cornerRadius
path.stroke()
}
}
}
let image = UIImage(named: "big-teddy-bear.jpg")
let imageView = RoundedImageView()
imageView.image = image
Let me know if that's the sort of thing you're looking for.
A little explanation:
As I'm sure you've found, the "border" that iOS can apply isn't perfect, and shows the corners for some reason. I found a few other solutions but none seemed to work. The reason this is a subclass of UIView, and not UIImageView, is that drawRect: is not called for subclasses of UIImageView. I am not sure about the performance of this code, but it seems good from my (limited) testing
Original code:
class RoundedImageView: UIView {
var image: UIImage? {
didSet {
if let image = image {
self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
}
}
private class func frameForImage(image: UIImage) -> CGRect {
return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
override func drawRect(rect: CGRect) {
if let image = self.image {
self.image?.drawInRect(rect)
let path = UIBezierPath(roundedRect: rect, cornerRadius: 50)
UIColor.whiteColor().setStroke()
path.lineWidth = 10
path.stroke()
}
}
}
let image = UIImage(named: "big-teddy-bear.jpg")
let imageView = RoundedImageView()
imageView.image = image
imageView.layer.cornerRadius = 50
imageView.clipsToBounds = true
I am Copying the same Question asked Before Question.
I have tried the solutions given and was not able to solve it since sizetofit was not effective when I use Autolayout.
The expected display is like below.
Edit
In my original answer I was using the paragraph style of the label. Turns out that for multi-line labels this actually prevents the label from being multi-line. As a result I removed it from the calculation. See more about this in Github
For those of you more comfortable with using Open Source definitely look at TTTAttributedLabel where you can set the label's text alignment to TTTAttributedLabelVerticalAlignmentTop
The trick is to subclass UILabel and override drawTextInRect. Then enforce that the text is drawn at the origin of the label's bounds.
Here's a naive implementation that you can use right now:
Swift
#IBDesignable class TopAlignedLabel: UILabel {
override func drawTextInRect(rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
var labelStringSize = stringTextAsNSString.boundingRectWithSize(CGSizeMake(CGRectGetWidth(self.frame), CGFloat.max),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawTextInRect(CGRectMake(0, 0, CGRectGetWidth(self.frame), ceil(labelStringSize.height)))
} else {
super.drawTextInRect(rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.blackColor().CGColor
}
}
Swift 3
#IBDesignable class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelStringSize = stringTextAsNSString.boundingRect(with: CGSize(width: self.frame.width,height: CGFloat.greatestFiniteMagnitude),
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawText(in: CGRect(x:0,y: 0,width: self.frame.width, height:ceil(labelStringSize.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
Objective-C
IB_DESIGNABLE
#interface TopAlignedLabel : UILabel
#end
#implementation TopAlignedLabel
- (void)drawTextInRect:(CGRect)rect {
if (self.text) {
CGSize labelStringSize = [self.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.frame), CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName:self.font}
context:nil].size;
[super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),ceilf(labelStringSize.height))];
} else {
[super drawTextInRect:rect];
}
}
- (void)prepareForInterfaceBuilder {
[super prepareForInterfaceBuilder];
self.layer.borderWidth = 1;
self.layer.borderColor = [UIColor blackColor].CGColor;
}
#end
Since I used IBDesignable you can add this label to a storyboard and watch it go, this is what it looks like for me
If you're not restricted by having UILabel of fixed size, instead of aligning the text within a UILabel, simply use ≥ constraint on the given label to change the size of it.
It's the most elegant solution using Auto Layout. Don't forget to set numberOfLines to zero though.
You can use UITextView instead of UILabel:
Uncheck "Scrolling enabled"
Uncheck "Editable"
Uncheck "Selectable"
Set background color to ClearColor
I had the same problem, and this is how I solved it. I just edited the Baseline under Attribute Inspector for the Label. Set it to "Align Centers".
Instead, I changed the Bottom Space Constant to priority #250 and solved my problem. And my label has height constant with <= constant
You would do that by removing the minimum height.
If you need the minimum height to something else below the label then you would use a container view that resized based on the label contents but used a minimum.
Auto layout only work with edges/sizes of controller, not with controllers content.so its not a good idea to use auto layout to display your label text on top of first line.
according to me sizetofit is a best option to do so.
I used #Daniel Golasko's solution and whenever the text inside the UILabel was longer than the UILabel could contain, the text would start moving down instead of staying aligned to top.
I changed this line to make sure the text is aligned properly
[super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),MIN(ceilf(labelStringSize.height), self.frame.size.height))];
I had a similiar issue where there were 3 labels. The middle label could have much longer text than the other two, so its height could grow much larger.
Like this:
I set the middle label's bottom space constraint to be >= the bottom label.
That solved my problem.
Here's an improvement on the Swift 3 solution by Daniel Galasko (here you can also set the maximum line number without an offset on the top):
import UIKit
#IBDesignable class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelString = stringTextAsNSString.boundingRect(with: CGSize(width: frame.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
super.drawText(in: CGRect(x: 0, y: 0, width: frame.width, height: ceil(labelString.size.height) > frame.height ? frame.height : ceil(labelString.size.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
There is an easy solution for cases where the height of a label doesn't need to be constant: put your Label in a Stack View. Be sure to add leading and trailing constants to the Stack View. Here is a screenshot of how to do it in storyboard:
Swift 4
You should subclass UILabel and override text display rendering.
class UITopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
guard let string = text else {
super.drawText(in: rect)
return
}
let size = (string as NSString).boundingRect(
with: CGSize(width: rect.width, height: .greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin],
attributes: [.font: font],
context: nil).size
var rect = rect
rect.size.height = size.height.rounded()
super.drawText(in: rect)
}
}
You can try if button [button setContentVerticalAlignment:UIControlContentVerticalAlignmentTop];
Edit :
You can try with this if you want to use label only:
https://stackoverflow.com/a/11278660/1223897
For me, I didn't set the height constraint, the text always grows from the top of the label.
The constraints for this label are top, left, right.
By the way, my label has fixed line numbers, so no worries about the height.
#IBInspectable var alignTop: Bool = false
func setAlignTop() {
let text = self.text!
let lines = text.characters.split(separator: "\n").count
if lines < self.numberOfLines {
var newLines = ""
for _ in 0..<(self.numberOfLines - lines) {
newLines = newLines.appending("\n ")
}
self.text! = text.appending(newLines)
}
}
override var text: String? {
didSet {
if alignTop {
self.setAlignTop()
}
}
}
use this my class, you can change text alignment by contentMode.
supported case: .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight
Swift4
import Foundation
import UIKit
#IBDesignable
class UIAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let text = text as NSString? {
func defaultRect(for maxSize: CGSize) -> CGRect {
let size = text
.boundingRect(
with: maxSize,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [
NSAttributedStringKey.font: font
],
context: nil
).size
let rect = CGRect(
origin: .zero,
size: CGSize(
width: min(frame.width, ceil(size.width)),
height: min(frame.height, ceil(size.height))
)
)
return rect
}
switch contentMode {
case .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight:
let maxSize = CGSize(width: frame.width, height: frame.height)
var rect = defaultRect(for: maxSize)
switch contentMode {
case .bottom, .bottomLeft, .bottomRight:
rect.origin.y = frame.height - rect.height
default: break
}
switch contentMode {
case .right, .topRight, .bottomRight:
rect.origin.x = frame.width - rect.width
default: break
}
super.drawText(in: rect)
default:
super.drawText(in: rect)
}
} else {
super.drawText(in: rect)
}
}
}
In the Interface Builder, just make the height <= some value instead of =. This will enable to text to start at the top and expand the height as needed. For example, I have a label with a height proportional to the size of the main view. So my height constraint looks like this:
Height Constraint
I have used table view in my application, and within that I have used the single line separator given by tableview.
How can I make a transparent separator in a tableview?
If anybody has any ideas/code snippets/useful links, I would greatly appreciate it.
it's very simple
in your viewdidload use this
[yourTableView setSeparatorColor:[UIColor clearColor]];
Just in case someone is still looking for solution with 'transparent' separator of any height:
Inside custom UITableViewCell:
override func layoutSubviews() {
super.layoutSubviews()
let mutablePath = CGMutablePath()
mutablePath.addRect(self.bounds)
// frame for your separator
let frame = CGRect(x: 0, y: 55, width: self.bounds.size.width, height: 5)
mutablePath.addRect(frame)
let mask = CAShapeLayer()
mask.path = mutablePath
mask.fillRule = CAShapeLayerFillRule.evenOdd
self.layer.mask = mask
if !(self.superview?.isKind(of: UITableView.self) ?? false) {
self.superview?.layer.mask = mask
}
}
Just use
tableView.separatorColor = .clear