Creating padding through Interface Builder - ios

I was using this post as reference: Create space at the beginning of a UITextField. And in this post, there is a very helpful class adding padding in a textfield. However, the only way that I know how to use this class is for me to programmatically create a textfield. But instead, I would like to use this class with an IBOutlet. Here is the TextField Class:
class TextField: UITextField {
let padding = UIEdgeInsets(top: 0, left: 35, bottom: 0, right: 5);
override func textRectForBounds(bounds: CGRect) -> CGRect {
return self.newBounds(bounds)
}
override func placeholderRectForBounds(bounds: CGRect) -> CGRect {
return self.newBounds(bounds)
}
override func editingRectForBounds(bounds: CGRect) -> CGRect {
return self.newBounds(bounds)
}
private func newBounds(bounds: CGRect) -> CGRect {
print("paisjdpfij")
var newBounds = bounds
newBounds.origin.x += padding.left
newBounds.origin.y += padding.top
newBounds.size.height -= padding.top + padding.bottom
newBounds.size.width -= padding.left + padding.right
return newBounds
}
}
And here is my attempt to use it with my IBOutlet:
#IBOutlet weak var firstNameTextField: TextField!
override func viewDidLoad() {
super.viewDidLoad()
firstNameTextField = TextField()
}
However, the there is still no padding in the textfield. Anybody have a solution to this problem?
Now, I used this code:
let paddingView = UIView(frame: CGRect(x: 0.0, y: 10.0, width: 5.0, height: 20.0))
firstNameTextField.leftView = paddingView
firstNameTextField.leftViewMode = .Always
to add padding on the left side of the textfield. However, I would also like some padding on the bottom as well. And there doesn't seem to be a simple solution for adding a bottom padding.

you can create category/extension or create custom text field as you did. And implement this 2 methods and play with it by changing different bounds.
import UIKit
class CustomTextField: UITextField
{
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
override func textRectForBounds(bounds: CGRect) -> CGRect
{
return CGRectMake(bounds.origin.x + 10, bounds.origin.y + 8, bounds.size.width - 20, bounds.size.height - 16);
}
override func editingRectForBounds(bounds: CGRect) -> CGRect
{
return self.textRectForBounds(bounds);
}
}
For more detail you can refer this Nate Flink's answer at here:Set padding for UITextField with UITextBorderStyleNone

Related

UITextField context menu (cut, paste, etc) is not showing

The context menu with the standard text actions (cut, paste, copy, look up) is not showing up when I long press or double tap the text field. Instead, both of these actions seem to select the content of the text field and allow me to drag the selection elsewhere.
I am using the following custom UITextField subclass:
class BMTextField: UITextField {
required init(placeholder: String? = nil, text: String? = nil) {
super.init(frame: CGRect.zero)
font = UIFont(name: Fonts.Light, size: 18)
textColor = Colors.Gray51
layer.borderColor = Colors.Gray51Transparent.cgColor
layer.borderWidth = 2
layer.cornerRadius = 0
self.placeholder = placeholder
self.text = text
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let padding = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0);
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
}
Here is how I'm adding it to my superview (using SnapKit):
lastNameTextField.textContentType = .familyName
lastNameTextField.autocorrectionType = .yes
showingSuperview.addSubview(lastNameTextField)
lastNameTextField.snp.makeConstraints {
make in
make.height.equalTo(M.standardControlHeight)
make.top.equalTo(firstNameTextField.snp.bottom).offset(S.thirtyTwo)
make.left.right.equalToSuperview()
}
I tried creating a brand new project with a text field, and there, everything works smoothly.

UIButton align image left and center text

Introduction:
I have a class, which inherits from UIButton. In this class I want to update properties, like titleEdgeInsets, imageEdgeInsets, contentHorizontalAlignment.
My first approach was to use layoutSubviews:
override func layoutSubviews() {
super.layoutSubviews()
// update properties
}
The layoutSubviews creates an infinity loop, so that I've searched for an alternative method.
My Question:
Is it a common way, to use the willMove method for updating UIButton properties?
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
// update properties
}
If not, why?
My goal is to align the imageView of the button left (with padding) and center the text.
UPDATE:
I need the button frame.size and the bounds.width to calculate the position of the text and the image view
All the properties you mentioned above can be set in the init of the UIButton there is absolutely no need to set them in layoutSubviews or willMove(toWindow.
layoutSubviews will be called multiple times so setting these properties again n agin in here makes no sense. willMove(toWindow will be called when button is added to some view and button is loaded but you dont have to wait till then to set these properties. Because you already have a subclass of button, so I would suggest doing
class SomeButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.contentHorizontalAlignment = .center
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
By the way creating a subclass of UIButton is not recommended, so if you wanna simply assign these properties to your button you can rather have a extension to UIButton
extension UIButton {
func applyStyle() {
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.contentHorizontalAlignment = .center
}
}
EDIT:
Is this what you want??
No matter what the text is, text is always in centre and image is to its left with 10 pixel padding
EDIT 2:
As OP has confirmed that, he wants the button to be styled as showed in images above, posting the code to achieve the same
class SomeButton: UIButton {
var titleFont: UIFont! = nil
var textSize: CGFloat = 0
let imageWidth: CGFloat = 20
let buttonHeight: CGFloat = 30
override init(frame: CGRect) {
super.init(frame: frame)
self.titleFont = titleLabel!.font
self.setTitle("here", for: .normal)
self.setTitleColor(UIColor.red, for: .normal)
self.setImage(UIImage(named: "hand"), for: .normal)
self.backgroundColor = UIColor.green
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
if let string = self.title(for: .normal) {
textSize = string.widthOfString(usingFont: self.titleFont)
//30 because imageWidth + 10 padding
return CGRect(origin: CGPoint(x: 30, y: 0), size: CGSize(width: textSize + 30, height: buttonHeight))
}
return CGRect.zero
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: imageWidth, height: buttonHeight))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var intrinsicContentSize: CGSize {
//60 because you need eauql padding on both side 30 + 30 = 60
return CGSize(width: textSize + 60, height: buttonHeight)
}
}
extension String {
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}
Hope it helps

IBDesignable inside UITableViewCell not rendered properly

I have a grid of custom "text below image" UIButtons inside a static UITableViewCell.
To make 8 UIButtons form 4x2 grid, I put them into 2 horizontal UIStackViews and wrapped them with a vertical UIStackView.
You can see details in the screenshots below.
It runs perfect on simulator and real devices, but not rendered properly in Storyboard. I'd like to know if these are something missing in my code or some settings I should check/uncheck in Storyboard.
Runtime behavior(works well)
In Storyboard(chaos)
Outline
Custom Button:
import UIKit
#IBDesignable class YPTextBelowImageButton: UIButton {
#IBInspectable var heightRatio: CGFloat = 0.8 {
didSet {
self.setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(x: 0, y: 0, width: contentRect.size.width, height: contentRect.size.height * heightRatio)
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(x: 0, y: contentRect.size.height * heightRatio, width: contentRect.size.width, height: contentRect.size.height * (1 - heightRatio))
}
}
Preview:
func prepareForInterfaceBuilder not working
override func prepareForInterfaceBuilder() {
self.imageView?.contentMode = .scaleAspectFit
self.titleLabel?.textAlignment = .center
self.setNeedsDisplay()
self.setNeedsLayout()
}

How to increase height for a custom UINavigationBar class in Swift

Update: I ended up by hiding the default navigation bar and added a UIView which looks same as the navigation bar. This may not sound good but instead of patching into the UINavigationBar this is good.
This is my custom UINavigationBar class which I have created to increase the height of navigation bar in my app. It doesn't work for me. Here's the code.
class PPBaseNavigationBar: UINavigationBar {
///The height you want your navigation bar to be of
static let navigationBarHeight: CGFloat = 83.0
///The difference between new height and default height
static let heightIncrease: CGFloat = navigationBarHeight - 44
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
let shift = PPBaseNavigationBar.heightIncrease/2.0
///Transform all view to shift upward for [shift] point
self.transform = CGAffineTransform(translationX: 0, y: -shift)
}
override func layoutSubviews() {
super.layoutSubviews()
let shift = PPBaseNavigationBar.heightIncrease/2.0
///Move the background down for [shift] point
var classNamesToReposition: Array<String>?
if #available(iOS 10.0, *) {
classNamesToReposition = ["_UIBarBackground"]
} else {
classNamesToReposition = ["_UINavigationBarBackground"]
}
for view: UIView in self.subviews {
if (classNamesToReposition?.contains(NSStringFromClass(view.classForCoder)))! {
let bounds: CGRect = self.bounds
var frame: CGRect = view.frame
frame.origin.y = bounds.origin.y + shift - 20.0
frame.size.height = bounds.size.height + 20.0
view.frame = frame
}
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let amendedSize:CGSize = super.sizeThatFits(size)
let newSize:CGSize = CGSize.init(width: amendedSize.width, height: PPBaseNavigationBar.navigationBarHeight)
return newSize;
}
}
All the method gets called except override func sizeThatFits(_ size: CGSize) -> CGSize {...} not sure, why?
It is not permissible to change the navigation bar object or modify its bounds, frame, or alpha values directly.
Modifying the Navigation Bar Object Directly
You can use custom view as navigation bar. Customize view as per your requirement(e.g change height) and hide the default navigation bar as
self.navigationController?.setNavigationBarHidden(true, animated: false)
layoutSubviews works as well override func sizeThatFits(_ size: CGSize).
so do not worry if it is not sizeThatFits called. I checked in layoutSubviews Navigationbar height are changing.
for view: UIView in self.subviews {
if (classNamesToReposition?.contains(NSStringFromClass(view.classForCoder)))! {
let bounds: CGRect = self.bounds
var frame: CGRect = view.frame
frame.origin.y = bounds.origin.y + shift - 20.0
frame.size.height = bounds.size.height + 150.0
view.frame = frame
}
}
Try like this, this worked for me in Swift 3:
extension UINavigationBar {
override open func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.size.width, height: "Your custom height")
}
}
// MARK: - View life cycle methods
class DVNavigationController: UINavigationController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
navigationBar.frame.size.height = "Your custom height"
}
}
Finally assign this custom UINavigationController class to your navigationController.
Change your code like below
open override func sizeThatFits(_ size: CGSize) -> CGSize {
let amendedSize:CGSize = super.sizeThatFits(size)
let newSize:CGSize = CGSize.init(width: amendedSize.width, height: PPBaseNavigationBar.navigationBarHeight)
return newSize;
}
Might be worked for you.

iOS 10 custom navigation bar height

I implemented custom navigation bar height, by subclassing it with following code
class TMNavigationBar: UINavigationBar {
///The height you want your navigation bar to be of
static let navigationBarHeight: CGFloat = 44.0
///The difference between new height and default height
static let heightIncrease:CGFloat = navigationBarHeight - 44
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
let shift = TMNavigationBar.heightIncrease/2
///Transform all view to shift upward for [shift] point
self.transform =
CGAffineTransformMakeTranslation(0, -shift)
}
override func layoutSubviews() {
super.layoutSubviews()
let shift = TMNavigationBar.heightIncrease/2
///Move the background down for [shift] point
let classNamesToReposition: [String] = ["_UINavigationBarBackground"]
for view: UIView in self.subviews {
if classNamesToReposition.contains(NSStringFromClass(view.dynamicType)) {
let bounds: CGRect = self.bounds
var frame: CGRect = view.frame
frame.origin.y = bounds.origin.y + shift - 20.0
frame.size.height = bounds.size.height + 20.0
view.frame = frame
}
}
}
override func sizeThatFits(size: CGSize) -> CGSize {
let amendedSize:CGSize = super.sizeThatFits(size)
let newSize:CGSize = CGSizeMake(amendedSize.width, TMNavigationBar.navigationBarHeight);
return newSize;
}
}
Following problem occurs only on iOS 10: (black space between bar & view)
No idea what's happening there. But in storyboard it's generated this warning, and there's no way to fix it in IB (warning only appears when i change subclass of navigation bar in IB).
Works on iOS 10, Swift 3.0:
extension UINavigationBar {
open override func sizeThatFits(_ size: CGSize) -> CGSize {
let screenRect = UIScreen.main.bounds
return CGSize(width: screenRect.size.width, height: 64)
}
}
I checked Interface debugger and this is what i see (so basically it's trying to change navigation bar height, bit it's stays same and it's showing just black space - which is window color):
With later investigation i noticed that it's not calling: "_UINavigationBarBackground"
Then i checked view.classForCoder from fast enumeration, and discovered that key is changed to "_UIBarBackground", so i updated layoutSubviews():
override func layoutSubviews() {
super.layoutSubviews()
let shift = TMNavigationBar.heightIncrease/2
///Move the background down for [shift] point
let classNamesToReposition = isIOS10 ? ["_UIBarBackground"] : ["_UINavigationBarBackground"]
for view: UIView in self.subviews {
if classNamesToReposition.contains(NSStringFromClass(view.classForCoder)) {
let bounds: CGRect = self.bounds
var frame: CGRect = view.frame
frame.origin.y = bounds.origin.y + shift - 20.0
frame.size.height = bounds.size.height + 20.0
view.frame = frame
}
}
}
Cheers.

Resources