I added a left view to my UITextField via:
customTextField.leftView = searchIconView
customTextField.leftViewMode = .always
However, I later need to remove that icon from my customTextField and make it look like the original. You would think that you could just do this and it would reset:
customTextField.leftView = nil
customTextField.leftViewMode = .never
This did not work. It got rid of my search icon, but the padding of the left view still acted like it was there. It just is this weird whitespace.
I figured out the answer to my own question:
I had to leave the customTextField.leftViewMode as .always once I had set it. Then, when I want to revert back to the original, I just set a new view for the Left View that is zero everything except has the original padding for the customTextField.
customTextField.leftView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: customTextField.originalLeftPadding, height: 0)))
I get the original text field padding by subclassing UITextField for my customTextField, then I just hold onto the original left padding as a variable:
class CustomTextField : UITextField {
var originalLeftPadding: CGFloat = 0
override init(frame: CGRect) {
super.init(frame: frame)
originalLeftPadding = leftPadding
}
}
Related
I am trying to set an underline on my UITextFields. I have tried a couple of methods but none of them seem to work. After looking through a couple of websites, the most suggested method is the following:
extension UITextField {
func setUnderLine() {
let border = CALayer()
let width = CGFloat(0.5)
border.borderColor = UIColor.lightGray.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width-10, height: self.frame.size.height)
border.borderWidth = width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
}
I can't think of any reason as to why the code above would not work, but all the answers I saw were posted a couple of years ago.
Could someone please let me know what I am doing wrong?
One problem I see with the code that you posted is that it won't update the layer if the text field gets resized. Each time you call the setUnderLine() function, it adds a new layer, then forgets about it.
I would suggest subclassing UITextField instead. That code could look like this:
class UnderlinedTextField: UITextField {
let underlineLayer = CALayer()
/// Size the underline layer and position it as a one point line under the text field.
func setupUnderlineLayer() {
var frame = self.bounds
frame.origin.y = frame.size.height - 1
frame.size.height = 1
underlineLayer.frame = frame
underlineLayer.backgroundColor = UIColor.blue.cgColor
}
// In `init?(coder:)` Add our underlineLayer as a sublayer of the view's main layer
required init?(coder: NSCoder) {
super.init(coder: coder)
self.layer.addSublayer(underlineLayer)
}
// in `init(frame:)` Add our underlineLayer as a sublayer of the view's main layer
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.addSublayer(underlineLayer)
}
// Any time we are asked to update our subviews,
// adjust the size and placement of the underline layer too
override func layoutSubviews() {
super.layoutSubviews()
setupUnderlineLayer()
}
}
That creates a text field that looks like this:
(And note that if you rotate the simulator to landscape mode, the UnderlineTextField repositions the underline layer for the new text field bounds.)
Note that it might be easier to just add a UIView to your storyboard, pinned to the bottom of your text field and one pixel tall, using your desired underline color. (You'd set up the underline view using AutoLayout constraints, and give it a background color.) If you did that you wouldn't need any code at all.
Edit:
I created a Github project demonstrating both approaches. (link)
I also added a view-based underline to my example app. That looks like this:
I'm trying to use a custom view as an accessory view over the keyboard, for various reasons, in this case, it is much preferred over manual keyboard aligning because of some other features.
Unfortunately, this is a dynamic view that defines its own height. The constraints all work fine outside of the context of an accessoryView without errors, and properly resizing
When added as a keyboardAccessoryView it seems to impose a height of whatever the frame is at the time and break other height constraints
It appears as:
"<NSLayoutConstraint:0x600003e682d0 '_UIKBAutolayoutHeightConstraint' Turntable.ChatInput:0x7fb629c15050.height == 0 (active)>"
(where 0 would correspond to whatever height had been used at initialization
It is also labeled accessoryHeight which should make it easy to remove, but unfortunately, before I can do this, I'm getting unsatisfiable constraints and the system is tossing my height constraints
Tried:
in the inputAccessoryView override, I tried to check for the constraints and remove it, but it doesn't exist at this time
setting translatesAutoresizing...Constraints = false
tl;dr
Using a view as a KeyboardAccessoryView is adding its own height constraint after the fact, can I remove this?
Looks like keyboard doesn't like inputAccessoryView with height constraint. However you still can have inputAccessoryView with dynamic height by using frame (it is still possible to use constraints inside your custom inputAccessoryView).
Please check this example:
import UIKit
final class ViewController: UIViewController {
private let textField: UITextField = {
let view = UITextField()
view.frame = .init(x: 100, y: 100, width: 200, height: 40)
view.borderStyle = .line
return view
}()
private let customView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.frame.size.height = 100
view.autoresizingMask = .flexibleHeight // without this line height won't change
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textField)
textField.inputAccessoryView = customView
textField.becomeFirstResponder()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.customView.frame.size.height = 50
self.textField.reloadInputViews()
}
}
}
I am currently developing an application which has two UILabels inside a Vertical StackView, two UITextFields inside a Vertical StackView, and both of those Vertical StackViews inside one Horizontal StackView as such:
I have constraints put in place. When the application runs on bigger devices such as an iPhone 11, it looks perfect as you can see here:
But if I switch to a smaller device like the iPhone 8 you can see the lines hug the edge of the phone as such:
The way I make the underline for the TextField is by using a class I created called StyledTextField. It looks like this:
class StyledTextField: UITextField {
var bottomLine = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
styleTextField()
}
private func styleTextField() {
font = UIFont(name: "Quicksand", size: UIFont.labelFontSize)
// Create the bottom line
bottomLine.frame = CGRect(x: 0, y: frame.height - 2, width: frame.width, height: 2)
bottomLine.backgroundColor = Environment.Colours.primary.cgColor
// Remove border on text field
borderStyle = .none
// Add the line to the text field
layer.addSublayer(bottomLine)
}
func makeUnderlineLight() {
bottomLine.backgroundColor = Environment.Colours.primaryAssisted.cgColor
}
}
In the Storyboard, I assign the UITextField class to be that of the "StyledTextField".
Additionally, in my viewDidLoad function on the Controller that deals with the UITextFields, I call the setUpUI() function which does the following:
func setUpUI() {
title = "Add Task"
taskNameTextField.attributedPlaceholder = NSAttributedString(string: "Name", attributes: [NSAttributedString.Key.foregroundColor: Environment.Colours.lightGray])
moreInfoTextField.attributedPlaceholder = NSAttributedString(string: "(Optional)", attributes: [NSAttributedString.Key.foregroundColor: Environment.Colours.lightGray])
setUpDatePicker()
moreInfoTextField.makeUnderlineLight()
if isEditing() {
showTaskToEdit()
}
view.backgroundColor = Environment.Colours.secondary
}
As you can see, I call the makeUnderlineLight() StyledTextField function once inside there.
Thank you!
Simple two-step solution:
Rewrite styleTextField so that it keeps a reference to the underline layer and removes the underline layer if it already exists, before making a new one.
Move the call to styleTextField() to layoutSubviews. (Don't forget to call super.)
select Your "More Info:" label and set horizontal compress resistanse priority to 1000
it ca be found here:
I have a UIButton extension to make some of my buttons have a round shape:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let originalContentSize = super.intrinsicContentSize
let height = originalContentSize.height + 5
layer.cornerRadius = height / 2
layer.masksToBounds = true
return CGSize(width: originalContentSize.width + 35, height: height)
}
}
This works as intended for the buttons within my views. However, I also have some buttons in my navigation bars, which have been affected by this extension and now appear in their containers with extra padding that is rather unsightly.
This is how I usually create my navigation bar buttons:
private func navBarButton() -> UIBarButtonItem {
let button = UIBarButtonItem(image: UIImage(named: "x"), style: .plain, target: self, action: #selector(x))
button.tintColor = .black
return button
}
Before I added my extension to the UIButton class, my navbar buttons used to look like this:
Now, they look like this:
which I do not want. How do I make my navbar buttons go back to what they looked like before adding my extension?
Change your extension from an override to a static function so it only happens when you explicitly instantiate one:
extension UIButton {
static func createAsRound(size: CGSize) -> UIButton {
let button = UIButton(type: .system)
button.frame = CGRect(origin: CGPoint.zero, size: size)
button.layer.cornerRadius = size.height/2
button.layer.masksToBounds = true
return button
}
}
Usage:
let myButton = UIButton.createAsRound(size: CGSize(width: 40, height: 40))
EDIT. If you are using auto layout:
In your extension, add to 'createAsRound`
button.translatesAutoresizingMaskIntoConstraints = false
Then add another method that will only be called as needed:
func makeRound() {
button.layer.cornerRadius = self.frame.height/2
self.setNeedsDisplay()
}
This edit is untested code. I'll edit as needed. (The first code I've used many times and with auto layout).
Obviously the one line of code to add is, well, actually just a convenience. Once you set the auto mask flag you're telling iOS to use auto layout. Setting a "frame" versus setting explicit origin/size constraints - particularly if using layout anchors can be done in the extension as long as you've already instantiated the button's superview.
Dynamically changing the size? Once you glean the UIViewController hierarchy, it should be simple. One like place is viewDidLayoutSubviews where the instantiated UIButton should have had auto layout compute it's frame... at which point you just make it round.
This may not exactly fit your needs, but it should be a good start to creating exactly a round UIButton only when you want to.
Why not to use an explicit class that will do exactly what you need?
class RoundedButton: UIButton {
open override var intrinsicContentSize: CGSize {
let originalContentSize = self.intrinsicContentSize
let height = originalContentSize.height + 5
layer.cornerRadius = height / 2
layer.masksToBounds = true
return CGSize(width: originalContentSize.width + 35, height: height)
}
}
So i am using a custom function to format an subview that I am adding to a UICollectionViewCell. It is from Brian Voong's public project here: https://github.com/purelyswift/facebook_feed_dynamic_cell_content/blob/master/facebookfeed2/ViewController.swift.
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerate() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
What is interesting, is that in my UICollectionView I add a SubView to a single cell, and set the background color to white. The background is white when I comment out the line which sets the background for the subview, and no background color is set when I uncomment out the line setting the visually formatted constraints for the subview.
Here are the two lines which clobber each other:
func chronicleOneClicked(sender: UIButton) {
point1view.backgroundColor = UIColor.whiteColor()
addSubview(point1view)
//When the below is commented the background of point1view disappears
//addConstraintsWithFormat("|-50-[v0]-50-|", views: point1view)
}
when I do print(subviews) i see that the UIView with the white background color is the highest in the view stack (top of the stack). When i print out subviews[subviews.count-1].backgroundColor I get the Optional(UIDeviceWhiteColorSpace 1 1) which is what I expect. it is strange because the color is not displayed.
I am not sure how to go about seeing what is happening behind the scenes to confirm that the background is being set at all in the latter case.
This all happens in a class for the UiCollectionViewCell which I am using as the class of one of my UICollectionView Cells which can be viewed in its entirety here:
https://gist.github.com/ebbnormal/edb79a15dab4797946e0d1f6905c2dd0
Here is a screen shot from both cases, the first case is where the line addConstraintsWithFormat is commented out, and the second case is where it is uncommented: The subview of point1subview is highlighted with a white background in the first case.
This is how I setup the views. It all happens in a class that overrides UICollectionViewCell
class myClass : UICollectionViewCell {
var chronicle: BrowsableChronicle? {
didSet{
//etc.
point1.addTarget(self, action: #selector(chronicleOneClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let point1 : PointButtonView = {
let pointView = PointButtonView(frame: CGRectMake(0, 0, 25, 25 ))
return pointView
}()
//NOTE here is where I create the view, whose background doesn't display
let point1view : UIView = {
let pointView = UIView(frame: CGRectMake( 0, 0, 200, 270))
pointView.backgroundColor = UIColor.whiteColor()
let title = UILabel(frame: CGRectMake(0, 0, 200, 21))
title.font = UIFont(name:"HelveticaNeue-Bold", size: 16.0)
pointView.addSubview(title)
let summary = UILabel(frame: CGRectMake(0, 0, 190, 260))
summary.lineBreakMode = NSLineBreakMode.ByWordWrapping
summary.numberOfLines = 4
summary.font = UIFont(name:"HelveticaNeue", size: 12.5)
pointView.addSubview(summary)
let button = UIButton(frame: CGRectMake(0, 200, 190, 30))
button.backgroundColor = UIColor(red:0.00, green:0.90, blue:0.93, alpha:1.0)
pointView.addSubview(button)
pointView.tag = 100
return pointView
}()
//NOTE: here is where I add the subview to the UICollectionViewCell view
func chronicleOneClicked(sender: UIButton){
addSubview(point1view)
addConstraintsWithFormat("H:|-20-[v0]-20-|", views: point1view)
//TODO anytime i add a constraint here the background color leaves!
print(subviews[subviews.count-1].backgroundColor) //Prints white
}
}
UPDATE: I thought maybe it was related to this issue :
UITableViewCell subview disappears when cell is selected
Where the UICollectionViewCell is selected, and therefore iOS automatically sets the backgroundColor to clear. The problem is, that I implemented this class extension of UIView to see when didSet is called on the backgroundColor and when it is set to clear, i set it to white. However, it only calls didSet on the backgroundColor once, when i first set the color of the view. Here is the code I used to override the UIView class:
class NeverClearView: UIView {
override var backgroundColor: UIColor? {
didSet {
print("background color is being set")
if backgroundColor == UIColor.clearColor() {
print("set to a clear color")
backgroundColor = UIColor.whiteColor()
}
}
}
}
The difference you are seeing is obviously caused by a view frame resulting in zero width or zero height.
Let's explain how the drawing system works.
Every view has a layer that draws its background color in its bounds, which are specified by the view frame. Then every subview is drawn. However, the subviews are not limited by the frame unless you set UIView.clipsToBounds to true.
What you are seeing means the a container view has a zero frame (either width or height) but its subviews have correct frame, therefore they are displayed correctly.
There are multiple reasons why this could happen, for example:
You are setting translatesAutoresizingMaskIntoConstraints to false to some system view (e.g. the content view of the UICollectionView).
You have a constraint conflict, resulting in some important constraint to be removed (you should see a warning).
You are missing some constraints. Specifically, I don't see you setting vertical constraints.
You should be able to debug the problem using the view debugger in Xcode. Just open your app, click the view debugger button and print the recursive description of the cell. You should see a frame that is zero.