I want to make a variadic method to accept any number of textfields and a UIColor as inputs and my intention is to add a bottom line of that specific color to all thus textfields inside method.
How can I make such a method in Swift?
Probably something like this would do:
func applyColour(toTextfields textfields: UITextField..., colour: UIColor) {
for textfield in textfields {
// Apply the colour here.
}
}
Common.addBottonLine(color: UIColor.white, userNameTextField, passwordTextField)
Method definition
class func addBottonLine(color: UIColor, _ txtFields: UITextField... ) -> Void {
for textF in txtFields {
var bottomLine = CALayer()
bottomLine.frame = CGRect.init(origin: CGPoint.init(x: 0, y: textF.frame.height - 2) , size: CGSize.init(width: textF.frame.width, height: 2 ))
bottomLine.backgroundColor = color.cgColor
textF.borderStyle = .none
textF.layer.addSublayer(bottomLine)
}
}
Use this function to add bottom line
func addBottomLabel(_ color: UIColor) {
let lbl1 = UILabel()
lbl1.backgroundColor = color
addSubview(lbl1)
addVisualConstraints(["H:|[label]|", "V:[label(1)]|"], forSubviews: ["label": lbl1])
}
Use :
let textFields = [your textfield array]
for textFields in textFields
{
textFields.addBottomLabel(.yourcolor)
}
Related
Can anyone help me break Apple's approach to create the phone app keypad screen? I have tried creating it with UIStackView with buttons of 1:1 and many other approaches but all in vain. I want to use such a screen in my own project for learning
This is what I want and this is what I have so far, Xcode view of storyboard
Part of solution may involve custom (round, green) buttons. To create a button with a red background (as a caution against critical action), I subclassed NSButton:
class ColorButton: NSButton
And its draw(...) method.
That gave me control over appearance. All other behavior inherited from NSButton.
Additionally, the #IBDesignable and #IBInspectable tags exposed the custom control in InterfaceBuilder.
That was the easy part. Getting the appearance just right was a non-trivial effort (for me) as Cocoa buttons have some subtle behaviors (e.g. disabled, highlight). Responding to desktop color options (such as Dark Mode) is also a consideration.
Here is code for my ColorButton which, again, was to alter the background color not the shape.
#IBDesignable
class ColorButton: NSButton {
#IBInspectable var backgroundColor: NSColor = NSColor.controlBackgroundColor
{ didSet { needsDisplay = true } }
#IBInspectable var textColor: NSColor = NSColor.controlTextColor
override func draw(_ dirtyRect: NSRect)
{
// fill in background with regular or highlight color
var backgroundColor = (isHighlighted ? self.backgroundColor.highlight(withLevel: 0.75) : isEnabled ? self.backgroundColor : NSColor.gray)!
if isEnabled == false { backgroundColor = NSColor.lightGray }
let textColor = (isEnabled ? self.textColor : NSColor.disabledControlTextColor )
var rect: NSRect = NSRect(x: 7.0, y: 5.0, width: self.frame.width - 14.0, height: self.frame.height - 13.0) // self.frame// dirtyRect
var path = NSBezierPath(roundedRect: rect, xRadius: 4.0, yRadius: 4.0)
backgroundColor.setFill()
NSColor.darkGray.setStroke()
path.lineWidth = 0
path.fill()
path.stroke()
let font = NSFont(name: "Helvetica", size: (rect.height) * 0.66)
let text = title as NSString
let textFontAttributes = [convertFromNSAttributedStringKey(NSAttributedString.Key.font): font!, convertFromNSAttributedStringKey(NSAttributedString.Key.foregroundColor): textColor] as [String : Any]
let size = text.size(withAttributes: convertToOptionalNSAttributedStringKeyDictionary(textFontAttributes))
let location = NSPoint(x: ((self.frame.width - size.width) / 2.0), y: ((self.frame.height - size.height) / 2.0) - 3.0)
text.draw(at: location, withAttributes: convertToOptionalNSAttributedStringKeyDictionary(textFontAttributes))
}// draw
}
Above was developed under Swift 3. Swift 4 migration tool added:
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertFromNSAttributedStringKey(_ input: NSAttributedString.Key) -> String {
return input.rawValue
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToOptionalNSAttributedStringKeyDictionary(_ input: [String: Any]?) -> [NSAttributedString.Key: Any]? {
guard let input = input else { return nil }
return Dictionary(uniqueKeysWithValues: input.map { key, value in (NSAttributedString.Key(rawValue: key), value)})
}
https://www.youtube.com/watch?v=vI7m5RTYNng
Well explained and has code as well.
I'm not able to add kern space into the tab bar attributed text.
The UITabBar in question is a custom tabBar, you can find the code below.
I'm using the "attributed key" dictionary to add attributes to the items title, but I'm having an issue with the kern space.
class ProfileTabBar: UITabBar {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setStyle()
}
required override init(frame: CGRect) {
super.init(frame: frame)
self.setStyle()
}
func setStyle() {
self.tintColor = Style.shared.primary1
// Disable the default border
self.layer.borderWidth = 0.0
self.clipsToBounds = true
// Create a new bottom border
let bottomLine = CALayer()
let screenWidth = UIScreen.main.bounds.width
//let viewForFrame = self.superview ?? self
//let screenWidth = viewForFrame.bounds.width
bottomLine.frame = CGRect(x: 0.0, y: self.frame.height - 1, width: screenWidth, height: 2.0)
bottomLine.backgroundColor = UIColor(red: 235.0/255, green: 235.0/255, blue: 235.0/255, alpha: 1.0).cgColor
self.layer.addSublayer(bottomLine)
// Get the size of a single item
let markerSize = CGSize(width: screenWidth/CGFloat(self.items!.count), height: self.frame.height)
// Create the selection indicator
self.selectionIndicatorImage = UIImage().createSelectionIndicator(color: self.tintColor, size: markerSize , lineWidth: 3.0)
// Customizing the items
if let items = self.items {
for item in items {
item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -15)
let attributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font: UIFont(name: Style.shared.fontBold.fontName, size: 14) as Any,
NSAttributedStringKey.kern: NSNumber(value: 1.0)
]
item.setTitleTextAttributes(attributes, for: .normal)
}
}
}
All the attributes works except for the kern. What I'm doing wrong?
This question is old and there is an even older answer here. It appears that UITabBarItem appearance ignores NSAttributedString.Key.kern. That leaves us with a few options.
Subclass UITabBarItem this isn't easy because UITabBarItem inherits from UIBarItem which is an NSObject not a UIView.
Subclass UITabBar this can be done, but involves a decent amount of work for just some kern. You'll have to use UIButton instead of UITabBarItem so that the kern is applied.
You can add spacing using unicode characters in your title. This is really easy and can probably achieve the spacing you're looking for with just a few lines of code.
Unicode spacing:
U+0020 1/4 em
U+2004 1/3 em
U+2005 1/4 em
U+2006 1/6 em
U+2008 The width of a period “.”
U+2009 1/5 em (or sometimes 1/6 em)
You can use a unicode character in a String in Swift like this "\u{2006}". That means we can insert a small space between all the characters in our tabBarItem title. Like this:
extension String {
var withOneSixthEmSpacing: String {
let letters = Array(self)
return letters.map { String($0) + "\u{2006}" }.joined()
}
Using this for our tabBarItems:
self.navigationController.tabBarItem = UITabBarItem(
title: "Home".withOneSixthEmSpacing,
image: homeImage,
selectedImage: homeSelectedImage
)
Visually we end up with:
Instead of:
Another workaround is to subclass UITabBarController, and set the kerning in viewDidLayoutSubviews.
class FooTabBarController: UITabBarController {
private var tabBarButtons: [UIControl] {
tabBar.subviews.compactMap { $0 as? UIControl }
}
private var tabBarButtonLabels: [UILabel] {
tabBarButtons.compactMap { $0.subviews.first { $0 is UILabel } as? UILabel }
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tabBarButtonLabels.forEach {
if let attributedText = $0.attributedText {
let mutable = NSMutableAttributedString(attributedString: attributedText)
mutable.addAttribute(.kern, value: 0.5, range: .init(location: 0, length: mutable.length))
$0.attributedText = mutable
$0.sizeToFit()
}
}
}
}
The caveats to this solution are:
It is somewhat fragile. It can break if Apple changes the view structure in the tab bar, ie if they stop using UIControl, or if they change the subview heirarchy.
It isn't all that efficient because the kerning has to be set every layout cycle.
I loved #DoesData's answer, it really helped me out a lot.
Here's a more "swifty" version of it I came up with if it helps anyone:
extension String {
var withAddedSpacing: String {
Array(self)
.compactMap { String($0) }
.joined(separator: "\u{2006}")
}
}
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 am attempting to use radial gradience within my app on a background UIView. My issue comes to play, where I want to update the view colors of the gradience multiple times. I have no errors with my code, but I can't seem to figure out how to get around this.
What I have tried is reloading the Input Views within the regular UIView as-well as the gradience class; remove the subview of the uiview, and adding a new view to the screen, which worked for only change of set colors; and I have looked over the internet, but can't seem to resolve this. All I want is for the UIView to update its colors based on the new color parameters I give it.
Here is my radial gradience code:
import UIKit
class RadialGradient: UIView {
var innerColor = UIColor.yellow
var outterColor = UIColor.red
override func draw(_ rect: CGRect) {
let colors = [innerColor.cgColor, outterColor.cgColor] as CFArray
let endRadius = min(frame.width, frame.height)
let center = CGPoint(x: bounds.size.width/2, y: bounds.size.height/2)
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
UIGraphicsGetCurrentContext()!.drawRadialGradient(gradient!,
startCenter: center,
startRadius: 0.0,
endCenter: center,
endRadius: endRadius,
options: CGGradientDrawingOptions.drawsAfterEndLocation)
}
}
Here is where I am using it:
import UIKit
class TestIssuesVC: UIViewController {
var check : Bool = false
#IBAction func buttonClicked(_ sender: Any) {
if check == true {
backgroundsetting.removeFromSuperview()
print("Why wont you change to purple and black?????")
cheapFix(inner: UIColor.purple, outter: UIColor.black)
} else {
backgroundsetting.removeFromSuperview()
cheapFix(inner: UIColor.red, outter: UIColor.blue)
check = true
}
}
func cheapFix(inner: UIColor, outter: UIColor) {
let backgroundsetting = RadialGradient()
backgroundsetting.innerColor = inner
backgroundsetting.outterColor = outter
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
let backgroundsetting = RadialGradient()
override func viewWillAppear(_ animated: Bool) {
backgroundsetting.innerColor = UIColor.green
backgroundsetting.outterColor = UIColor.red
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
}
I see two things.
Your cheapFix method never updates the backgroundsetting property. It creates its own local variable of the same name. So you are actually adding new views over and over but each is sent to the back so you only ever see the first one. This is why nothing ever appears to change.
None of that is necessary. Simply create one RadialGradient view. When you want its colors to change, simply update its colors. That class needs to be fixed so it redraws itself when its properties are updated.
Make the following change to the two properties in your RadialGradient class:
var innerColor = UIColor.yellow {
didSet {
setNeedsDisplay()
}
}
var outterColor = UIColor.red {
didSet {
setNeedsDisplay()
}
}
Those changes will ensure the view redraws itself when its colors are updated.
I have some UIbuttons in my app that have a number indicator before the text. Right now, I am just using string interpolation to display the number before the string as seen below.
fruitButton.setTitle("\(fruitCounter) Fruits", forState: UIControlState.Normal)
I need the number to stand out more, rather than just blending in with the title text. Something as simple as a circle surrounding it will do the trick, as seen in the design below:
.
I did some research on Attributed Strings in Swift. However, I am just seeing many examples on changing text properties. EG - changing the text color and size of the number indicator. I can't figure out how to add a circle behind.
I do not want this circle to be an image, simply for scalibility purposes. For instance, if that number ends up being 2 digits long, I need the circle to stretch to oval. My thought was using a small view behind the number, and then just applying a color / alpha / radius to achieve the look I need.
So to wrap this up: How can I add circles behind my number indicators using Attributed Strings in Swift?
You should create a UIView subclass that will contain these two elements. Create a corresponding .xib file that includes those entities or implement those entities in code. You can also implement touchesBegan so that this view can act like a button OR add a button instead of a text label and implement a protocol to fire every time the button is hit. I've started this for you with some semi-arbitrary numbers. You'll have to play with them to get them just right.
class UICoolButton: UIView {
var labelText: NSString?
var circledNumber: Int?
var circleSubview: UIView?
init(frame: CGRect, labelText: NSString, circledNumber: Int) {
super.init(frame: frame);
self.labelText = labelText;
self.circledNumber = circledNumber;
self.layer.cornerRadius = frame.height/3
self.clipsToBounds = true
addCircledNumber()
addTextLabel(labelText)
}
func setColors(numberColor:UIColor, backgroundColor: UIColor) {
self.backgroundColor = backgroundColor
self.circleSubview?.backgroundColor = numberColor
}
override init(frame: CGRect) {
super.init(frame: frame);
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
}
func addTextLabel(text: NSString) {
let origin = CGPoint(x:self.frame.width * 0.4, y:self.frame.height/10)
let size = CGSize(width: self.frame.width/2, height: self.frame.height * 0.8)
let rect = CGRect(origin: origin, size: size)
let label = UILabel(frame: rect)
let attributes: [String : AnyObject] = [NSFontAttributeName : UIFont(name: "Verdana", size: 16.0)!,
NSForegroundColorAttributeName : UIColor.whiteColor()]
label.attributedText = NSAttributedString(string: text, attributes: attributes)
self.addSubview(label)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
func addCircledNumber() {
let height = self.frame.height * 0.4;
let circleDimensions = CGSize(width: height , height:height)
let origin = CGPointMake(self.frame.width * 0.15, self.frame.height - self.frame.height/1.5)
let circleSubview = UIView(frame: CGRect(origin: origin, size: circleDimensions))
circleSubview.layer.cornerRadius = height/2;
circleSubview.backgroundColor = UIColor.darkGrayColor()
let labelHeight = height * 0.8;
let xPosition = circleSubview.bounds.origin.x + 3
let yPosition = circleSubview.bounds.origin.y + 2
let labelOrigin = CGPoint(x: xPosition, y: yPosition)
let labelRect = CGRect(origin: labelOrigin, size: CGSize(width: labelHeight, height: labelHeight))
let numberLabel = UILabel(frame: labelRect);
numberLabel.textAlignment = NSTextAlignment.Center
let numberAsString = NSString(format: "%i", circledNumber!) as String
let attributes: [String : AnyObject] = [NSFontAttributeName : UIFont(name: "Verdana", size: 16.0)!,
NSForegroundColorAttributeName : UIColor.whiteColor()]
numberLabel.attributedText = NSAttributedString(string: numberAsString, attributes: attributes)
circleSubview.addSubview(numberLabel);
self.circleSubview = circleSubview
self.addSubview(circleSubview)
}
Then in your View Controller, use the initilizer I wrote:
func addCoolButton() {
let rect = CGRect() //choose the frame for your button here
let button = UICoolButton(frame: rect, labelText: "Example", circledNumber: 10);
let backgroundColor = UIColor(red: 243/255.0 , green: 93/255.0, blue: 118/255.0, alpha: 1.0)
let numberColor = UIColor(red: 252/255.0, green: 118.0/255.0, blue: 135/255.0, alpha: 1.0)
button.setColors(numberColor, backgroundColor: backgroundColor)
self.view.addSubview(button)
}
I did sample some thing like your purpose with autolayout
with code here:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *content;
#property (weak, nonatomic) IBOutlet UILabel *label;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.content.layer.cornerRadius = MIN(self.content.bounds.size.width, self.content.bounds.size.height)/2;
self.content.clipsToBounds = true;
}
#end
result with number is 1234:
result with number is 1: