I am new to swift. I have two UITextFields email and password. I want to add only bottom border to text fields and for that I am using the following method,
extension UITextField {
func setBottomBorderWithLayer() {
self.borderStyle = .none
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
self.layer.shadowOpacity = 1.0
self.layer.shadowRadius = 0.0
}
}
And this is how I am calling the method
emailTextField.setBottomBorderWithLayer()
passwordTextField.setBottomBorderWithLayer()
Now the problem is setBottomBorderWithLayer() method only setting bottom border for only emailTextField and not for passwordTextField.
Can anyone explain that where is the problem?
Thanks in advance.
We need more details to be able to define where the error is, but I suggest another type of implementation that will meet your need.
Create a class that extends from UITextField, in this class you can define all the necessary layout settings, facilitating reuse, as you will only need to define the class in TextField from Storyboard -> TextField -> Identity Inspector -> Custom Class -> Class.
Class example:
import UIKit
class BottomBorderTextField: UITextField {
override init (frame: CGRect) {
super.init (frame: frame)
addBottomBorder()
}
required init? (coder: NSCoder) {
super.init (coder: coder)
addBottomBorder()
}
func addBottomBorder() {
// Logic for adding style
layer.backgroundColor = UIColor.white.cgColor
layer.borderColor = UIColor.gray.cgColor
layer.borderWidth = 0.0
layer.cornerRadius = 5
layer.masksToBounds = false
layer.shadowRadius = 2.0
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 1.0, height: 1.0)
layer.shadowOpacity = 1.0
layer.shadowRadius = 1.0
}
}
Hope this helps.
I agree with Silas, but just adding on here...
If you want to create a colorful bottom bar to a textfield, this also works:
import Foundation
import UIKit
class Utilities {
static func styleTextField(_ textfield:UITextField) {
// Create the bottom line
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: textfield.frame.height - 2, width: textfield.frame.width, height: 2)
bottomLine.backgroundColor = UIColor.init(red: 116/255, green: 203/255, blue: 128/255, alpha: 1).cgColor
// Remove border on text field
textfield.borderStyle = .none
// Add the line to the text field
textfield.layer.addSublayer(bottomLine)
}
Related
Hello i have some views with rounded corners, and I'd like to apply a shadow to this views.
SO first I round the view :
view.layer.cornerRadius = 15
view.clipsToBounds = true
then I apply the shadow :
func dropShadow(scale: Bool = true) {
self.layer.masksToBounds = false
self.layer.cornerRadius = 15
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.layer.shadowOffset = CGSize(width: 3, height: 3)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 5
}
view.dropShadow()
I got my rounded view with a shadow but the shadow is not rounded like my view. The shadow is not rounded at all
You cannot cast a shadow from a view whose clipsToBounds is true. If a view's masksToBounds is true, its clipsToBounds is true; they are the same thing.
If you want a shadow to appear to come from from a view that clips, you need to use two views: one that the user can see, with rounded corners and clipsToBounds set to true, and another that the user can't see because it's behind the first one, also with rounded corners, but with clipsToBounds set to false, to cast the shadow.
class ShadowView : UIView {
override init(frame: CGRect) {
super.init(frame:frame)
self.isOpaque = true
self.backgroundColor = .black
self.dropShadow()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dropShadow() {
self.layer.masksToBounds = false
self.layer.cornerRadius = 15
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 3, height: 3)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 5
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let r = CGRect(x: 100, y: 100, width: 100, height: 100)
let v = UIImageView(frame:r)
v.image = UIImage(named:"marsSurface.jpg")
v.clipsToBounds = true
v.backgroundColor = .red
v.layer.cornerRadius = 15
self.view.addSubview(ShadowView(frame:r))
self.view.addSubview(v)
}
}
Note that neither view is a subview of the other, nor do they have a superview that clips, as that would clip the shadow.
Trying to have a single view handle both corner rounding and shadow logic is not recommended. The best way to create the effect you are looking for is to wrap your rounded view with another parent view that owns the shadow.
This can be best illustrated with a playground. Try this code out in a playground.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let shadowView = UIView()
shadowView.frame = CGRect(x: 50, y: 200, width: 200, height: 100)
shadowView.backgroundColor = nil
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0, height: 4.0)
shadowView.layer.shadowRadius = 8.0
shadowView.layer.shadowOpacity = 1
let roundedView = UIView()
roundedView.frame = shadowView.bounds
roundedView.backgroundColor = .gray
roundedView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner,.layerMaxXMinYCorner]
roundedView.layer.cornerRadius = 10
roundedView.layer.masksToBounds = true
shadowView.addSubview(roundedView)
view.addSubview(shadowView)
view.backgroundColor = UIColor.white
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Don't forget to set the shadowPath property on your shadow view's layer in either viewWillLayoutSubviews or layoutSubviews. If this property is set to nil, UIKit has to perform off screen rendering to figure out how to draw the shadow rect.
I created a custom UIButton with this initialiser :
class CustomButton : UIButton{
override init(frame: CGRect) {
super.init(frame: frame)
setUpButtoninClass(frame)
addTarget(self, action: #selector(handleTap), for:.touchUpInside )
}
fileprivate func setUpButtoninClass(_ frame: CGRect) {
let padding : CGFloat = 16
self.frame = frame
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowOpacity = 0.3
layer.shadowOffset = .zero
layer.shadowRadius = 10
layer.cornerRadius = frame.width/2
backgroundColor = UIColor(white: 0.9, alpha: 1)
let buttonView = UIView(frame: frame)
buttonView.layer.cornerRadius = frame.width/2
buttonView.backgroundColor = .white
addSubview(buttonView)
let imageView = UIImageView(image: UIImage(named: "pen")?.withRenderingMode(.alwaysTemplate))
imageView.tintColor = UIColor(white: 0.7, alpha: 1)
buttonView.addSubview(imageView)
imageView.anchor(top: buttonView.topAnchor, leading: buttonView.leadingAnchor, bottom: buttonView.bottomAnchor, trailing: buttonView.trailingAnchor, padding: UIEdgeInsets.init(top: padding, left: padding, bottom: padding, right: padding))
}
#objc func handleTap(){
print("I'm here")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}}
In the initialiser I'm adding a target but when I actually initialise the custom button in the VC the #selector method (handleTap) is not called.
This is the implementation of custom Button in VC:
class ViewController: UIViewController {
let circularButton = CustomButton(frame: CGRect(x: 0, y: 0, width: 70, height: 70))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(circularButton)
circularButton.center = view.center
}
I also tried to add the target when initialising the CustomButton in the VC but nothing changed.
I would like to know where I'm making a mistake in setting up the button.
EDIT 1 :
this is the Debug View Hierarchy
OMG, after debug your code, buttonView and imageView is on the top. Button is behide. You can set the color to debug it more easily. Delete 2 views above make your code works perfectly
I think it's your fault here,
Touch is not detected because you added an ImageView to the top of UIButton.
Try this, or this one,
buttonView.isUserInteractionEnabled = true
imageView.isUserInteractionEnabled = true
I have a custom button with some shadow effect, following class i am using to create a custom button which i got from stackoverflow. It users CAShapeLayer to give effect of shadow on button.
import UIKit
class CustomButton: UIButton {
var shadowLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if shadowLayer == nil {
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
layer.insertSublayer(shadowLayer, at: 0)
//layer.insertSublayer(shadowLayer, below: nil) // also works
}
}
}
Following is an image of screen where i have created 4 buttons using this CustomButton Class which works fine.
when any button from above four is clicked i changed it's following property so it can look like an active button.
// to make button active
func setSelectedButton(sender:CustomButton){
//sender.backgroundColor = UIColor.red
sender.shadowLayer.fillColor = UIColor(named: "headerColor")?.cgColor
sender.setTitleColor(UIColor.white, for: .normal)
}
// to make button inactive
func setUnselected(sender:CustomButton){
//sender.backgroundColor = UIColor.init(red: 80/255, green:101/255, blue: 161/255, alpha: 1)
sender.shadowLayer.fillColor = UIColor.white.cgColor
sender.setTitleColor(.black, for: .normal)
}
Everything works fine. Now what i want is whenever view appears i want the first button to be selected by default which is routineButton to do that i have written following code in viewWillAppearMethod
override func viewWillAppear(_ animated: Bool) {
self.navigationItem.title = navigationTitle
self.view.makeToastActivity(.center)
self.routineButton.sendActions(for: .touchUpInside) //default selection of routineButton
loadEvaluationList(userId: 8, evaluationType: "RT") {
self.view.hideToastActivity()
}
}
when self.routineButton.sendActions(for: .touchUpInside) executes it call setSelectedButton(sender:CustomButton) method which gives error at following line
sender.shadowLayer.fillColor = UIColor(named: "headerColor")?.cgColor
which says shadowLayer is nil. This problem occur only when i try to set default selected button on viewWillAppear Method otherwise it works perfact.
I think the problem occur because shadowLayer property of CustomButton is not initialised at time of viewWillAppear.
so anyone knows what should i do? it will be helpful. Thank you in advance.
Move the initialization code for shadowLayer in the init methods, something like this:
import UIKit
class CustomButton: UIButton {
var shadowLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.initShadowLayer()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initShadowLayer()
}
func initShadowLayer() {
if shadowLayer == nil {
shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
layer.insertSublayer(shadowLayer, at: 0)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// Reset the path because bounds could have been changed
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
shadowLayer.shadowPath = shadowLayer.path
}
}
viewWillAppear is called before layoutSubviews of the button and shadowLayer is not initialized yet. Also try not to call sendActions(for: .touchUpInside), instead call the setSelectedButton function if you just want to configure the button appearance.
I can't figure out how to code a drop shadow on a label. I have a score label that changes so just photoshopping text with shadows wont be possible. I need to code it so it automatically has a blurry shadow behind the text at all times. Can anyone come with some examples or help?
People saying this is a duplicate, the "duplicate" is about drop shadows on UIView, mine is about UILabel. It's not the same thing.
Give this a try - you can run it directly in a Playground page:
import UIKit
import PlaygroundSupport
let container = UIView(frame: CGRect(x: 0, y: 0, width: 600, height: 400))
container.backgroundColor = UIColor.lightGray
PlaygroundPage.current.liveView = container
var r = CGRect(x: 40, y: 40, width: 300, height: 60)
let label = UILabel(frame: r)
label.font = UIFont.systemFont(ofSize: 44.0)
label.textColor = .white
label.frame = r
label.text = "Hello Blur"
container.addSubview(label)
label.layer.shadowColor = UIColor.black.cgColor
label.layer.shadowRadius = 3.0
label.layer.shadowOpacity = 1.0
label.layer.shadowOffset = CGSize(width: 4, height: 4)
label.layer.masksToBounds = false
Play around with different values for the shadow Color, Opacity, Radius and Offset
Result:
UILabel has a property for changing its shadow, the image below shows the property in attributes inspector and the result.
Result of that effect on label
You can write an extension and use it. Place the extension code outside of class ViewController.
I like subtle shadow.
extension UILabel {
func textDropShadow() {
self.layer.masksToBounds = false
self.layer.shadowRadius = 2.0
self.layer.shadowOpacity = 0.2
self.layer.shadowOffset = CGSize(width: 1, height: 2)
}
static func createCustomLabel() -> UILabel {
let label = UILabel()
label.textDropShadow()
return label
}
}
On your label simply call this method
myLabel.textDropShadow()
works fine but add shadow to ALL label, not to text.
in this case:
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let shadow = NSShadow()
shadow.shadowColor = UIColor.blue
shadow.shadowBlurRadius = 10
let attrs: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 36),
.foregroundColor: UIColor.red,
.shadow: shadow
]
let s = "MY TEXT"
let attributedText = NSAttributedString(string: s, attributes: attrs)
self.label.attributedText = attributedText
}
}
You will get:
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/CRMpg.png
**note:** You must add attributed string every time, as shadow is an attribute of string, not label, otherwise you can also derive class and override "setText". (keeping attributes inside the object in a a property you can set on init/setter)
Swift 4, IBInspectable using extension
extension UILabel {
#IBInspectable var isShadowOnText: Bool {
get {
return self.isShadowOnText
}
set {
guard (newValue as? Bool) != nil else {
return
}
if newValue == true{
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowRadius = 2.0
self.layer.shadowOpacity = 1.0
self.layer.shadowOffset = CGSize(width: 2, height: 2)
self.layer.masksToBounds = false
}
}
}
}
Swift 4 - Extension with shadow parameters:
// Label Shadow
extension UILabel {
func lblShadow(color: UIColor , radius: CGFloat, opacity: Float){
self.textColor = color
self.layer.masksToBounds = false
self.layer.shadowRadius = radius
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = CGSize(width: 1, height: 1)
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
On your label simply call this method
let titleColor = UIColor(red:0.08, green:0.08, blue:0.08, alpha:1.0)
titleLbl.lblShadow(color: titleColor, radius: 3, opacity: 0.25)
U can make a extension method for all UIView subclasses.
extension UIView {
func drawShadow(offset: CGSize, opacity: Float = 0.25, color: UIColor = .black, radius: CGFloat = 1) {
layer.masksToBounds = false
layer.shadowColor = color.cgColor
layer.shadowOffset = offset
layer.shadowOpacity = opacity
layer.shadowRadius = radius
}
}
As a simple starting point. I'm trying to create a custom button that has an activity indicator in the middle.
After a tap - it will indicate that it's thinking
I'm pretty much stuck at step 1 - adding the indicator to be centred in the surrounding view.
No matter want constraints I try it always appears in the top left corner.
What am I missing?
Here's my playground code.
//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit
class ConnectButton : UIView {
fileprivate let activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.color = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
activityIndicator.hidesWhenStopped = true;
activityIndicator.stopAnimating();
activityIndicator.activityIndicatorViewStyle = .white;
return activityIndicator;
}()
private func initView() {
translatesAutoresizingMaskIntoConstraints = true;
addSubview(activityIndicator);
NSLayoutConstraint.activate([
activityIndicator.centerYAnchor.constraint(equalTo: self.centerYAnchor),
activityIndicator.leftAnchor.constraint(equalTo: self.centerXAnchor)
])
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
self.initView();
}
override init(frame: CGRect) {
super.init(frame: frame);
self.initView();
}
public func startAnimating() {
activityIndicator.startAnimating();
}
public func stopAnimating() {
activityIndicator.stopAnimating();
}
}
let dimensions = (width:200, height: 50);
let connectButton = ConnectButton(
frame: CGRect(
x: dimensions.width / 2,
y: dimensions.height / 2,
width: dimensions.width,
height: dimensions.height
)
)
connectButton.startAnimating();
connectButton.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1);
PlaygroundPage.current.liveView = connectButton
You want to set translatesAutoresizingMaskIntoConstraints to false onto UIActivityIndicatorView. Otherwise, UIKit will create constraints based on the resizing masks set on the, which will conflict with the ones you already added.
fileprivate let activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.color = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
activityIndicator.hidesWhenStopped = true
activityIndicator.stopAnimating()
activityIndicator.activityIndicatorViewStyle = .white
activityIndicator. translatesAutoresizingMaskIntoConstraints = false
return activityIndicator
}()
Another solution: Create your UIActivityIndicator with a given frame from your superView as you already do, and simply call:
activityIndicator.center = self.center
And avoid the NSLayoutConstraints alltogether, unless you really need them for something.
However, as I mentioned in the commentsection, make sure you have your UIView laid out to the correct size before calling above line. Otherwise your result will be exactly the same.