How to customise UISlider height - ios

I have project in which have to customise UISlider element.
I wondering if someone knows how to change, or is it possible to change height of UISlide bar line.
I tried something like this but don't work:
let customBounds = CGRect(origin: bounds.origin,
size: CGSize(width: bounds.size.width, height: 15.0))
feedbackSlider.trackRectForBounds(customBounds)
Thanks

i hope that you want edit it in storyboard, and only the line size, use it in your custom UISlider
class CustomSlide: UISlider {
#IBInspectable var trackHeight: CGFloat = 2
override func trackRectForBounds(bounds: CGRect) -> CGRect {
//set your bounds here
return CGRect(origin: bounds.origin, size: CGSizeMake(bounds.width, trackHeight))
}
}

Overriding trackRect is a way to go, however if you're using additional UISlider's views like minimumValueImage, maximumValueImage you would also need to take their bound into account, otherwise they will overlap with slider’s track. As a shortcut you can simply use super's func:
Fixed version.
Swift 3+
#IBDesignable
class CustomSlider: UISlider {
/// custom slider track height
#IBInspectable var trackHeight: CGFloat = 3
override func trackRect(forBounds bounds: CGRect) -> CGRect {
// Use properly calculated rect
var newRect = super.trackRect(forBounds: bounds)
newRect.size.height = trackHeight
return newRect
}
}

You can override a method in you custom slider
For Objective-C
- (CGRect)trackRectForBounds:(CGRect)bounds {
CGRect rect = CGRectMake(0, 0, 100, 30);//change it to any size you want
return rect;
}
For Swift
override func trackRectForBounds(bounds: CGRect) -> CGRect {
var rect:CGRect = CGRectMake(0, 0, 100, 30)
return rect
}

Swift 3
class MySlide: UISlider {
#IBInspectable var height: CGFloat = 2
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: bounds.origin, size: CGSize(width: bounds.width, height: height))
}
}

Swift 4 version without thumbImage.
class CustomSlider: UISlider {
#IBInspectable var trackHeight: CGFloat = 2
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: bounds.origin, size: CGSize(width: bounds.width, height: trackHeight))
}
}

I think the answers above have a flaw: the origin of the track rect must be offset to account for the change in height of the track, otherwise it will show up off-center. Here is the way I did it:
class CustomSlider: UISlider {
#IBInspectable var sliderTrackHeight : CGFloat = 2
override func trackRect(forBounds bounds: CGRect) -> CGRect {
let originalRect = super.trackRect(forBounds: bounds)
return CGRect(origin: CGPoint(x: originalRect.origin.x, y: originalRect.origin.y + (sliderTrackHeight / 2)), size: CGSize(width: bounds.width, height: sliderTrackHeight))
}
}

U can easily get it done by subclassing UISlider. see following code
class CustomUISlider : UISlider
{
override func trackRectForBounds(bounds: CGRect) -> CGRect {
//set your bounds here
return bounds
}
}

You should be able to subclass the UISlider and then implement:
- (CGRect)trackRectForBounds:(CGRect)bounds
Just return the new CGRect here.

If you're using autolayout you can set a height constraint on the UISlider in the storyboard. if you need to change it at runtime - create an IBOutlet for the constraint and modify its .constant value.

Related

Increase UISlider height in Swift 3 without using transform [duplicate]

I've been searching for a way to make the UISlider progress bar taller, like increasing the height of the slider but couldn't find anything. I don't want to use a custom image or anything, just make it taller, so the UISlider doesn't look so thin. Is there an easy way to do this that I'm missing?
The accepted answer will undesirably change the slider's width in some cases, like if you're using a minimumValueImage and maximumValueImage. If you only want to change the height and leave everything else alone, then use this code:
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var newBounds = super.trackRect(forBounds: bounds)
newBounds.size.height = 12
return newBounds
}
Here's my recent swifty implementation, building on CularBytes's ...
open class CustomSlider : UISlider {
#IBInspectable open var trackWidth:CGFloat = 2 {
didSet {setNeedsDisplay()}
}
override open func trackRect(forBounds bounds: CGRect) -> CGRect {
let defaultBounds = super.trackRect(forBounds: bounds)
return CGRect(
x: defaultBounds.origin.x,
y: defaultBounds.origin.y + defaultBounds.size.height/2 - trackWidth/2,
width: defaultBounds.size.width,
height: trackWidth
)
}
}
Use this on a UISlider in a storyboard by setting its custom class
The IBInspectable allows you to set the height from the storyboard
For those that would like to see some working code for changing the track size.
class CustomUISlider : UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
//keeps original origin and width, changes height, you get the idea
let customBounds = CGRect(origin: bounds.origin, size: CGSize(width: bounds.size.width, height: 5.0))
super.trackRect(forBounds: customBounds)
return customBounds
}
//while we are here, why not change the image here as well? (bonus material)
override func awakeFromNib() {
self.setThumbImage(UIImage(named: "customThumb"), for: .normal)
super.awakeFromNib()
}
}
Only thing left is changing the class inside the storyboard:
You can keep using your seekbar action and outlet to the object type UISlider, unless you want to add some more custom stuff to your slider.
I found what I was looking for. The following method just needs to be edited in a subclass.
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var customBounds = super.trackRect(forBounds: bounds)
customBounds.size.height = ...
return customBounds
}
You could play with this, see what happens:
slider.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 2.0);

UIButton subview is offset in subclass

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.

Change the text layout in a UITextField

Below is an image of some UITextFields. Regarding the large one on the bottom, how do I get it to start text at the top left (not the middle), and how do I get them all to start a bit to the right. As you can see, they are awkwardly close to the left edge.
For textField, override following methods:
class InsetTextField: UITextField {
var inset: CGFloat = 10
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: 0)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: 0)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: 0)
}
}
For textView:
textView.contentInset = UIEdgeInsetsMake(inset, inset, inset, inset);
And you can be involved when the textView is done editing, check out this question.
You can sublcass to a custom TextField:
import UIKit
class CustomTextField: UITextField {
var inset:CGFloat = 12 // You can set the inset you want
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: 0)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: 0)
}
}
The result, you can see the inset of the CustomTextField:
Edit
By default, UITextFields only have one line. Based off your image, I'd assume you want the user to have space to type a paragraph. For paragraphs, it is better to use UITextViews. It'd be a simpler solution for your problem.
I always use TextViews for biographies because it just makes my life much easier.
add left padding of your textfield
let paddingVie = UIView(frame: CGRect(x: 0, y:0, width: 10, height: 10))
yourtextField.leftView = paddingVie
yourtextField.leftViewMode = .always
for textview add
yourTextVieName.textContainerInset =
UIEdgeInsetsMake(8,5,8,5); // top, left, bottom, right

Make UISlider height larger?

I've been searching for a way to make the UISlider progress bar taller, like increasing the height of the slider but couldn't find anything. I don't want to use a custom image or anything, just make it taller, so the UISlider doesn't look so thin. Is there an easy way to do this that I'm missing?
The accepted answer will undesirably change the slider's width in some cases, like if you're using a minimumValueImage and maximumValueImage. If you only want to change the height and leave everything else alone, then use this code:
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var newBounds = super.trackRect(forBounds: bounds)
newBounds.size.height = 12
return newBounds
}
Here's my recent swifty implementation, building on CularBytes's ...
open class CustomSlider : UISlider {
#IBInspectable open var trackWidth:CGFloat = 2 {
didSet {setNeedsDisplay()}
}
override open func trackRect(forBounds bounds: CGRect) -> CGRect {
let defaultBounds = super.trackRect(forBounds: bounds)
return CGRect(
x: defaultBounds.origin.x,
y: defaultBounds.origin.y + defaultBounds.size.height/2 - trackWidth/2,
width: defaultBounds.size.width,
height: trackWidth
)
}
}
Use this on a UISlider in a storyboard by setting its custom class
The IBInspectable allows you to set the height from the storyboard
For those that would like to see some working code for changing the track size.
class CustomUISlider : UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
//keeps original origin and width, changes height, you get the idea
let customBounds = CGRect(origin: bounds.origin, size: CGSize(width: bounds.size.width, height: 5.0))
super.trackRect(forBounds: customBounds)
return customBounds
}
//while we are here, why not change the image here as well? (bonus material)
override func awakeFromNib() {
self.setThumbImage(UIImage(named: "customThumb"), for: .normal)
super.awakeFromNib()
}
}
Only thing left is changing the class inside the storyboard:
You can keep using your seekbar action and outlet to the object type UISlider, unless you want to add some more custom stuff to your slider.
I found what I was looking for. The following method just needs to be edited in a subclass.
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var customBounds = super.trackRect(forBounds: bounds)
customBounds.size.height = ...
return customBounds
}
You could play with this, see what happens:
slider.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 2.0);

Resizing a UILabel to accommodate insets

I'm building a screen to scan barcodes, and I need to put a translucent screen behind some UILabels to improve visibility against light backgrounds.
Here's what the screen looks like now:
I'm setting the background color on the UILabel to get the translucent boxes. I've also created a custom UILabel subclass to allow me to set some padding between the edge of the UILabel and the text using this approach.
As you can see in the screen above, the UILabel doesn't resize correctly to take the padding into account. The "padding" just shifts the text over without changing the width of the label, causing the text to truncate.
Both of these labels will contain text of arbitrary lengths, and I really need the UILabel to dynamically resize.
What UILabel method can I override to increase the width of the label and factor in the padding?
Here's a label class that calculates sizes correctly. The posted code is in Swift 3, but you can also download Swift 2 or Objective-C versions.
How does it work?
By calculating the proper textRect all of the sizeToFit and auto layout stuff works as expected. The trick is to first subtract the insets, then calculate the original label bounds, and finally to add the insets again.
Code (Swift 5)
class NRLabel: UILabel {
var textInsets = UIEdgeInsets.zero {
didSet { invalidateIntrinsicContentSize() }
}
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
let insetRect = bounds.inset(by: textInsets)
let textRect = super.textRect(forBounds: insetRect, limitedToNumberOfLines: numberOfLines)
let invertedInsets = UIEdgeInsets(
top: -textInsets.top,
left: -textInsets.left,
bottom: -textInsets.bottom,
right: -textInsets.right
)
return textRect.inset(by: invertedInsets)
}
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: textInsets))
}
}
Optional: Interface Builder support
If you want to setup text insets in storyboards you can use the following extension to enable Interface Builder support:
#IBDesignable
extension NRLabel {
// currently UIEdgeInsets is no supported IBDesignable type,
// so we have to fan it out here:
#IBInspectable
var leftTextInset: CGFloat {
set { textInsets.left = newValue }
get { return textInsets.left }
}
// Same for the right, top and bottom edges.
}
Now you can conveniently setup your insets in IB and then just press ⌘= to adjust the label's size to fit.
Disclaimer:
All code is in the public domain. Do as you please.
Here is a Swift version of a UILabel subclass (same as #Nikolai's answer) that creates an additional padding around the text of a UILabel:
class EdgeInsetLabel : UILabel {
var edgeInsets:UIEdgeInsets = UIEdgeInsetsZero
override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
var rect = super.textRectForBounds(UIEdgeInsetsInsetRect(bounds, edgeInsets), limitedToNumberOfLines: numberOfLines)
rect.origin.x -= edgeInsets.left
rect.origin.y -= edgeInsets.top
rect.size.width += (edgeInsets.left + edgeInsets.right);
rect.size.height += (edgeInsets.top + edgeInsets.bottom);
return rect
}
override func drawTextInRect(rect: CGRect) {
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, edgeInsets))
}
}
Here is the C# version (usefull for Xamarin) based on Nikolai's code :
public class UIEdgeableLabel : UILabel
{
public UIEdgeableLabel() : base() { }
public UIEdgeableLabel(NSCoder coder) : base(coder) { }
public UIEdgeableLabel(CGRect frame) : base(frame) { }
protected UIEdgeableLabel(NSObjectFlag t) : base(t) { }
private UIEdgeInsets _edgeInset = UIEdgeInsets.Zero;
public UIEdgeInsets EdgeInsets
{
get { return _edgeInset; }
set
{
_edgeInset = value;
this.InvalidateIntrinsicContentSize();
}
}
public override CGRect TextRectForBounds(CGRect bounds, nint numberOfLines)
{
var rect = base.TextRectForBounds(EdgeInsets.InsetRect(bounds), numberOfLines);
return new CGRect(x: rect.X - EdgeInsets.Left,
y: rect.Y - EdgeInsets.Top,
width: rect.Width + EdgeInsets.Left + EdgeInsets.Right,
height: rect.Height + EdgeInsets.Top + EdgeInsets.Bottom);
}
public override void DrawText(CGRect rect)
{
base.DrawText(this.EdgeInsets.InsetRect(rect));
}
}
Swift 5 version of Nikolai Ruhe answer:
extension UIEdgeInsets {
func apply(_ rect: CGRect) -> CGRect {
return rect.inset(by: self)
}
}
class EdgeInsetLabel: UILabel {
var textInsets = UIEdgeInsets.zero {
didSet { invalidateIntrinsicContentSize() }
}
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
let insetRect = bounds.inset(by: textInsets)
let textRect = super.textRect(forBounds: insetRect, limitedToNumberOfLines: numberOfLines)
let invertedInsets = UIEdgeInsets(top: -textInsets.top,
left: -textInsets.left,
bottom: -textInsets.bottom,
right: -textInsets.right)
return textRect.inset(by: invertedInsets)
}
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: textInsets))
}}
In additions to Nikolai Ruhe's answer, you need to invalidate intrinsic content size for autolayout to properly recalculate the size changes. You would notice this issue if you change edgeInsets over the application lifecycle:
class NRLabel: UILabel {
var edgeInsets = UIEdgeInsetsZero {
didSet {
self.invalidateIntrinsicContentSize()
}
}
...
}
Here is an example of what I used for a simple 10 unit padding on the left and right of the label with rounded corners. Just set the label text to center it's self and make it's class IndentedLabel and the rest takes care of itself. To modify the padding just scale up or down rect.size.width += (x)
class IndentedLabel: UILabel {
var edgeInsets:UIEdgeInsets = UIEdgeInsetsZero
override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
var rect = super.textRectForBounds(UIEdgeInsetsInsetRect(bounds, edgeInsets), limitedToNumberOfLines: numberOfLines)
rect.size.width += 20;
return rect
}
override func drawTextInRect(rect: CGRect) {
self.clipsToBounds = true
self.layer.cornerRadius = 3
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, edgeInsets))
}
}
Here's a quick, hacky way to do it that you can understand more quickly. It's not as robust as Nikolai's, but it gets the job done. I did this when I was trying to fit my text in my UILabel within a UITableViewCell:
Set a width constraint for the UILabel
Connect the constraint via IBOutlet onto your code, either VC (custom cell class if you're doing an expanding table view cell)
Create a variable for the actual size of the text, then add the insets + the width size to the constraint and update the view:
let messageTextSize: CGSize = (messageText as NSString).sizeWithAttributes([
NSFontAttributeName: UIFont.systemFontOfSize(14.0)])
cell.widthConstraint.constant = messageTextSize.width + myInsetsOrWhatever
I haven't extensively tested it yet, you might have to play around with the exact CGFloat values that you add. I found that the right size isn't exactly width plus insets; it's a little larger than that. This makes sure that the width of the UILabel will always be at least the text size or larger.
Swift 5 .
You can create a custom UILabel class.
I've added 22 paddings to the left side of the content. When UILabel asks for intrinsicContentSize return by adding padding size you have added, I've added 22 and returned customized size. That's it.
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
let insets = UIEdgeInsets(top: 0, left: 22, bottom: 0, right: 0)
super.drawText(in: rect.inset(by: insets))
self.layoutSubviews()
}
// This will return custom size with flexible content size. Mainly it can be used in Chat.
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
size.width = 22 + size.width
return size
}

Resources