UIView animation causing label to twitch - ios

I have an IconView class that I use as a custom image for a Google Maps marker. All of the print statements show that the code is correctly executing. However, the "12:08" UILabel in circleView keeps on growing and shrinking (i.e. twitching). I can't figure out what the problem might be. I've tried manually setting the the font in the completion block, commenting out the adjustsFontSizeToFitWidth, changing the circleView to a UIButton.
import UIKit
class IconView: UIView {
var timeLabel: UILabel!
var circleView: UIView!
var clicked: Bool!
//constants
let circleViewWidth = 50.0
let circleViewHeight = 50.0
override init(frame:CGRect) {
super.init(frame : frame)
self.backgroundColor = UIColor(red: 47/255, green: 49/255, blue: 53/255, alpha: 0.0)
clicked = false
if !clicked {
//MAIN CIRCLE
print("init circle view")
circleView = UIView(frame: CGRect(x:0, y:0, width:circleViewWidth, height:circleViewHeight))
circleView.backgroundColor = UIColor(red: 47/255, green: 49/255, blue: 53/255, alpha: 1.0)
circleView.layer.cornerRadius = circleView.frame.size.height / 2.0
circleView.layer.masksToBounds = true
self.addSubview(circleView)
timeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: circleViewWidth, height: circleViewHeight/3.0))
timeLabel.center = circleView.center
timeLabel.text = "12:08"
timeLabel.textAlignment = .center
timeLabel.textColor = .white
timeLabel.numberOfLines = 0
timeLabel.font = UIFont.systemFont(ofSize: 11)
timeLabel.font = UIFont.boldSystemFont(ofSize: 11)
timeLabel.adjustsFontSizeToFitWidth = true
circleView.addSubview(timeLabel)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
let iconView = marker.iconView as! IconView
print("going to start animating")
if !iconView.clicked {
UIView.animate(withDuration: 0.2, animations: {
print("making this bigger now")
iconView.circleView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
})
{ (finished:Bool) -> Void in
print("DONE")
iconView.clicked = true
}
}
return true
}

Related

Removing Shadow Underneath Semi-Transparent UIView

I’ve been trying to add a drop shadow to a semi transparent UIView but the drop shadow is showing up underneath the view. Basically anywhere inside the outline of the view, I don't want to see any shadows. The location icon has no styling.
// Basic Shadow
self.myView.layer.shadowColor = UIColor.black.cgColor
self.myView.layer.shadowOpacity = 0.3
self.myView.layer.shadowOffset = CGSize(width: 0, height: 3)
self.myView.layer.shadowRadius = 0
The easiest way to do this is to use a custom UIView subclass with two CAShapeLayers...
For the "shadow" layer path, use a rounded-rect UIBezierPath that is slightly taller than the view, so it extends below the bottom.
Here's a quick example...
Custom View Class
class CustomView: UIView {
public var translucentColor: UIColor = .white.withAlphaComponent(0.7) { didSet { setNeedsLayout() } }
public var borderColor: UIColor = .init(red: 0.73, green: 0.84, blue: 0.96, alpha: 1.0) { didSet { setNeedsLayout() } }
public var borderWidth: CGFloat = 4 { didSet { setNeedsLayout() } }
public var shadowColor: UIColor = .black.withAlphaComponent(0.3) { didSet { setNeedsLayout() } }
public var cornerRadius: CGFloat = 20 { didSet { setNeedsLayout() } }
public var offset: CGFloat = 10 { didSet { setNeedsLayout() } }
private let shadowLayer = CAShapeLayer()
private let topLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() -> Void {
backgroundColor = .clear
layer.addSublayer(shadowLayer)
layer.addSublayer(topLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
var r = bounds
// rounded-rect path for visible border
let pth = UIBezierPath(roundedRect: r, cornerRadius: cornerRadius)
// translucent rounded-rect bordered properties
topLayer.path = pth.cgPath
topLayer.fillColor = translucentColor.cgColor
topLayer.lineWidth = borderWidth
topLayer.strokeColor = borderColor.cgColor
// rounded-rect path for "shadow" border
r.size.height += offset
let spth = UIBezierPath(roundedRect: r, cornerRadius: cornerRadius)
shadowLayer.path = spth.cgPath
shadowLayer.fillColor = UIColor.clear.cgColor
shadowLayer.lineWidth = borderWidth
shadowLayer.strokeColor = shadowColor.cgColor
}
}
Example Controller Class
class CustomViewTestVC: UIViewController {
let gradView = BasicGradientView()
let customView = CustomView()
// let's add a label between the gradient view and the custom view
// so we can confirm it's translucent
let testLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.textAlignment = .center
v.textColor = .systemBlue
v.font = .systemFont(ofSize: 34.0, weight: .bold)
v.text = "This is a test to confirm that the view and the \"shadow\" are both translucent while the border is opaque." // Tap anywhere to toggle this label's visibility."
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
[gradView, testLabel, customView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
gradView.widthAnchor.constraint(equalToConstant: 312.0),
gradView.heightAnchor.constraint(equalTo: gradView.widthAnchor, multiplier: 1.0),
gradView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testLabel.widthAnchor.constraint(equalTo: gradView.widthAnchor, constant: -4.0),
testLabel.heightAnchor.constraint(equalTo: gradView.heightAnchor, constant: 0.0),
testLabel.centerXAnchor.constraint(equalTo: gradView.centerXAnchor),
testLabel.centerYAnchor.constraint(equalTo: gradView.centerYAnchor),
customView.widthAnchor.constraint(equalTo: gradView.widthAnchor, constant: -90.0),
customView.heightAnchor.constraint(equalTo: gradView.heightAnchor, constant: -90.0),
customView.centerXAnchor.constraint(equalTo: gradView.centerXAnchor),
customView.centerYAnchor.constraint(equalTo: gradView.centerYAnchor),
])
gradView.endPoint = CGPoint(x: 1.0, y: 1.0)
gradView.colors = [
.red, .yellow, .cyan,
]
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
testLabel.isHidden.toggle()
}
}
Basic Gradient View
class BasicGradientView: UIView {
public var colors: [UIColor] = [.white, .black] { didSet { setNeedsLayout() } }
public var startPoint: CGPoint = CGPoint(x: 0.0, y: 0.0) { didSet { setNeedsLayout() } }
public var endPoint: CGPoint = CGPoint(x: 1.0, y: 0.0) { didSet { setNeedsLayout() } }
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
}
override func layoutSubviews() {
super.layoutSubviews()
gLayer.colors = colors.compactMap( {$0.cgColor })
gLayer.startPoint = startPoint
gLayer.endPoint = endPoint
}
}
This is the output -- tap anywhere to toggle the UILabel visibility:
Then add your imageView on top (or as a subview of the custom view):
Edit - to answer comment
We can get a shadow to show only on the outside by:
replacing the "fake-shadow shape layer" with a CALayer
using the bezier path as the layer's .shadowPath
creating a bezier path with a "hole" cut in it
use that path as a CAShapeLayer path
and then masking the shadow layer with that CAShapeLayer
Like this:
Here are updates to the above code as examples. Both classes are very similar, with the same custom properties that can be changed from their defaults. I've also added a UIImageView as a subview, to produce this output:
as before, tapping anywhere will toggle the UILabel visibility:
CustomViewA Class
class CustomViewA: UIView {
public var translucentColor: UIColor = .white.withAlphaComponent(0.5) { didSet { setNeedsLayout() } }
public var borderColor: UIColor = .init(red: 0.739, green: 0.828, blue: 0.922, alpha: 1.0) { didSet { setNeedsLayout() } }
public var borderWidth: CGFloat = 4 { didSet { setNeedsLayout() } }
public var cornerRadius: CGFloat = 20 { didSet { setNeedsLayout() } }
public var shadowColor: UIColor = .black.withAlphaComponent(0.3) { didSet { setNeedsLayout() } }
public var shadowOpacity: Float = 0.3
public var shadowOffset: CGSize = CGSize(width: 0.0, height: 8.0) { didSet { setNeedsLayout() } }
// shadowRadius is not used, but this allows us to treat both CustomViewA and CustomViewB the same
public var shadowRadius: CGFloat = 0 { didSet { setNeedsLayout() } }
public var image: UIImage? {
didSet {
imageView.image = image
}
}
private let imageView = UIImageView()
private let shadowLayer = CAShapeLayer()
private let topLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() -> Void {
backgroundColor = .clear
layer.addSublayer(shadowLayer)
layer.addSublayer(topLayer)
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0),
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
override func layoutSubviews() {
super.layoutSubviews()
var r = bounds
// rounded-rect path for visible border
let pth = UIBezierPath(roundedRect: r, cornerRadius: cornerRadius)
// translucent rounded-rect bordered properties
topLayer.path = pth.cgPath
topLayer.fillColor = translucentColor.cgColor
topLayer.lineWidth = borderWidth
topLayer.strokeColor = borderColor.cgColor
// rounded-rect path for "shadow" border
r.size.height += shadowOffset.height
let spth = UIBezierPath(roundedRect: r, cornerRadius: cornerRadius)
shadowLayer.path = spth.cgPath
shadowLayer.fillColor = UIColor.clear.cgColor
shadowLayer.lineWidth = borderWidth
shadowLayer.strokeColor = shadowColor.cgColor
}
}
CustomViewB Class
class CustomViewB: UIView {
public var translucentColor: UIColor = .white.withAlphaComponent(0.5) { didSet { setNeedsLayout() } }
public var borderColor: UIColor = .init(red: 0.739, green: 0.828, blue: 0.922, alpha: 1.0) { didSet { setNeedsLayout() } }
public var borderWidth: CGFloat = 4 { didSet { setNeedsLayout() } }
public var cornerRadius: CGFloat = 20 { didSet { setNeedsLayout() } }
public var shadowColor: UIColor = .black { didSet { setNeedsLayout() } }
public var shadowOpacity: Float = 0.7
public var shadowOffset: CGSize = CGSize(width: 0.0, height: 10.0) { didSet { setNeedsLayout() } }
public var shadowRadius: CGFloat = 6 { didSet { setNeedsLayout() } }
public var image: UIImage? {
didSet {
imageView.image = image
}
}
private let imageView = UIImageView()
private let shadowLayer = CALayer()
private let topLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() -> Void {
backgroundColor = .clear
layer.addSublayer(shadowLayer)
layer.addSublayer(topLayer)
// add a square (1:1) image view, 1/2 the width of self
// centered horizontally and vertically
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(imageView)
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0),
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
override func layoutSubviews() {
super.layoutSubviews()
// rounded-rect path for visible border
let pth = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
// translucent rounded-rect bordered properties
topLayer.path = pth.cgPath
topLayer.fillColor = translucentColor.cgColor
topLayer.lineWidth = borderWidth
topLayer.strokeColor = borderColor.cgColor
// we're going to mask the shadow layer with a "cutout" of the rounded rect
// the shadow is going to spread outside the bounds,
// so the "outer" path needs to be larger
// we'll make it plenty large enough
let bpth = UIBezierPath(rect: bounds.insetBy(dx: -bounds.width, dy: -bounds.height))
bpth.append(pth)
bpth.usesEvenOddFillRule = true
let maskLayer = CAShapeLayer()
maskLayer.fillRule = .evenOdd
maskLayer.path = bpth.cgPath
shadowLayer.mask = maskLayer
shadowLayer.shadowPath = pth.cgPath
shadowLayer.shadowOpacity = shadowOpacity
shadowLayer.shadowColor = shadowColor.cgColor
shadowLayer.shadowRadius = shadowRadius
shadowLayer.shadowOffset = shadowOffset
}
}
Example Controller Class - uses the BasicGradientView class above
class CustomViewTestVC: UIViewController {
let gradViewA = BasicGradientView()
let gradViewB = BasicGradientView()
let customViewA = CustomViewA()
let customViewB = CustomViewB()
// let's add a label between the gradient view and the custom view
// so we can confirm it's translucent
let testLabelA: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.textAlignment = .center
v.textColor = .systemRed
v.font = .systemFont(ofSize: 32.0, weight: .regular)
return v
}()
let testLabelB: UILabel = {
let v = UILabel()
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
[gradViewA, gradViewB, testLabelA, testLabelB, customViewA, customViewB].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradViewA.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
gradViewA.widthAnchor.constraint(equalToConstant: 300.0),
gradViewA.heightAnchor.constraint(equalTo: gradViewA.widthAnchor, multiplier: 1.0),
gradViewA.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testLabelA.widthAnchor.constraint(equalTo: gradViewA.widthAnchor, constant: -4.0),
testLabelA.heightAnchor.constraint(equalTo: gradViewA.heightAnchor, constant: 0.0),
testLabelA.centerXAnchor.constraint(equalTo: gradViewA.centerXAnchor),
testLabelA.centerYAnchor.constraint(equalTo: gradViewA.centerYAnchor),
customViewA.widthAnchor.constraint(equalTo: gradViewA.widthAnchor, constant: -84.0),
customViewA.heightAnchor.constraint(equalTo: gradViewA.heightAnchor, constant: -84.0),
customViewA.centerXAnchor.constraint(equalTo: gradViewA.centerXAnchor),
customViewA.centerYAnchor.constraint(equalTo: gradViewA.centerYAnchor),
gradViewB.topAnchor.constraint(equalTo: gradViewA.bottomAnchor, constant: 8.0),
gradViewB.widthAnchor.constraint(equalTo: gradViewA.widthAnchor, constant: 0.0),
gradViewB.heightAnchor.constraint(equalTo: gradViewB.widthAnchor, multiplier: 1.0),
gradViewB.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testLabelB.widthAnchor.constraint(equalTo: testLabelA.widthAnchor, constant: -0.0),
testLabelB.heightAnchor.constraint(equalTo: testLabelA.heightAnchor, constant: 0.0),
testLabelB.centerXAnchor.constraint(equalTo: gradViewB.centerXAnchor),
testLabelB.centerYAnchor.constraint(equalTo: gradViewB.centerYAnchor),
customViewB.widthAnchor.constraint(equalTo: customViewA.widthAnchor, constant: 0.0),
customViewB.heightAnchor.constraint(equalTo: customViewA.heightAnchor, constant: 0.0),
customViewB.centerXAnchor.constraint(equalTo: gradViewB.centerXAnchor),
customViewB.centerYAnchor.constraint(equalTo: gradViewB.centerYAnchor),
])
// let's setup the gradient views the same
gradViewA.colors = [
.init(red: 0.242, green: 0.591, blue: 0.959, alpha: 1.0),
.init(red: 0.113, green: 0.472, blue: 0.866, alpha: 1.0)
]
gradViewA.endPoint = CGPoint(x: 1.0, y: 1.0)
gradViewB.colors = gradViewA.colors
gradViewB.endPoint = gradViewA.endPoint
// let's give the two test labels the same properties
testLabelB.numberOfLines = testLabelA.numberOfLines
testLabelB.textAlignment = testLabelA.textAlignment
testLabelB.textColor = testLabelA.textColor
testLabelB.font = testLabelA.font
let s = "This is a test to confirm that the view and the \"shadow\" are both translucent while the border is opaque."
testLabelA.text = "CustomViewA\n" + s
testLabelB.text = "CustomViewB\n" + s
// set the .image property of both custom views
if let img = UIImage(named: "marker") {
customViewA.image = img
customViewB.image = img
} else {
if let img = UIImage(systemName: "mappin.and.ellipse")?.withTintColor(.white, renderingMode: .alwaysOriginal) {
customViewA.image = img
customViewB.image = img
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
testLabelA.isHidden.toggle()
testLabelB.isHidden.toggle()
}
}

How do you animate a floating label in swift

I'm trying to create a floating label similar to this: https://dribbble.com/shots/1254439--GIF-Float-Label-Form-Interaction
I've gotten down a decent amount of the actual functionality but I am having a problem with the actual animation part.
Here is my code:
import UIKit
class FloatingLabelInput: UITextField {
var floatingLabel: UILabel!// = UILabel(frame: CGRect.zero)
var floatingLabelHeight: CGFloat = 14
var button = UIButton(type: .custom)
var imageView = UIImageView(frame: CGRect.zero)
#IBInspectable
var _placeholder: String?
#IBInspectable
var floatingLabelColor: UIColor = UIColor.black {
didSet {
self.floatingLabel.textColor = floatingLabelColor
self.setNeedsDisplay()
}
}
#IBInspectable
var activeBorderColor: UIColor = UIColor.blue
#IBInspectable
var floatingLabelBackground: UIColor = Theme.current.backgroundColor {
didSet {
self.floatingLabel.backgroundColor = self.floatingLabelBackground
self.setNeedsDisplay()
}
}
#IBInspectable
var floatingLabelFont: UIFont = UIFont.systemFont(ofSize: 8) {
didSet {
self.floatingLabel.font = self.floatingLabelFont
self.font = self.floatingLabelFont
self.setNeedsDisplay()
}
}
#IBInspectable
var _backgroundColor: UIColor = Theme.current.backgroundColor {
didSet {
self.layer.backgroundColor = self._backgroundColor.cgColor
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self._placeholder = (self._placeholder != nil) ? self._placeholder : placeholder
placeholder = self._placeholder // Make sure the placeholder is shown
self.floatingLabel = UILabel(frame: CGRect.zero)
self.addTarget(self, action: #selector(self.addFloatingLabel), for: .editingDidBegin)
self.addTarget(self, action: #selector(self.removeFloatingLabel), for: .editingDidEnd)
self.addTarget(self, action: #selector(self.colorFloatingLabel), for: .editingDidBegin)
self.addTarget(self, action: #selector(self.removeColorFloatingLabel), for: .editingDidEnd)
}
#objc func colorFloatingLabel() {
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseInOut, animations: {
self.floatingLabel.textColor = UIColor.systemBlue
})
}
#objc func removeColorFloatingLabel() {
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseInOut, animations: {
self.floatingLabel.textColor = Theme.current.grays
})
}
// Add a floating label to the view on becoming first responder
#objc func addFloatingLabel() {
if self.text == "" {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .transitionCurlUp, animations: {
self.floatingLabel.textColor = self.floatingLabelColor
self.floatingLabel.font = self.floatingLabelFont
self.floatingLabel.text = self._placeholder
self.floatingLabel.layer.backgroundColor = UIColor.white.cgColor
self.floatingLabel.translatesAutoresizingMaskIntoConstraints = false
self.floatingLabel.clipsToBounds = true
self.floatingLabel.textAlignment = .center
self.floatingLabel.frame = CGRect(x: 0, y: 0, width: self.floatingLabel.frame.width+4, height: self.floatingLabel.frame.height+2)
self.layer.borderColor = self.activeBorderColor.cgColor
self.addSubview(self.floatingLabel)
self.floatingLabel.bottomAnchor.constraint(equalTo: self.topAnchor, constant: -2).isActive = true // Place our label 10 pts above the text field
self.placeholder = ""
})
}
// Floating label may be stuck behind text input. we bring it forward as it was the last item added to the view heirachy
self.bringSubviewToFront(subviews.last!)
self.setNeedsDisplay()
}
#objc func removeFloatingLabel() {
if self.text == "" {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .transitionCurlDown, animations: {
self.subviews.forEach{ $0.removeFromSuperview() }
self.setNeedsDisplay()
})
self.placeholder = self._placeholder
}
self.layer.borderColor = UIColor.black.cgColor
}
func addViewPasswordButton() {
self.button.setImage(UIImage(named: "ic_reveal"), for: .normal)
self.button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.button.frame = CGRect(x: 0, y: 16, width: 22, height: 16)
self.button.clipsToBounds = true
self.rightView = self.button
self.rightViewMode = .always
self.button.addTarget(self, action: #selector(self.enablePasswordVisibilityToggle), for: .touchUpInside)
}
func addImage(image: UIImage){
self.imageView.image = image
self.imageView.frame = CGRect(x: 20, y: 0, width: 20, height: 20)
self.imageView.translatesAutoresizingMaskIntoConstraints = true
self.imageView.contentMode = .scaleAspectFit
self.imageView.clipsToBounds = true
DispatchQueue.main.async {
self.leftView = self.imageView
self.leftViewMode = .always
}
}
#objc func enablePasswordVisibilityToggle() {
isSecureTextEntry.toggle()
if isSecureTextEntry {
self.button.setImage(UIImage(named: "ic_show"), for: .normal)
}else{
self.button.setImage(UIImage(named: "ic_hide"), for: .normal)
}
}
}
As you can see here, I tried UIView.animate but all that does is move it downwards as shown here (I changed the duration of the UIView.animate to make the animation really visible): https://m.imgur.com/a/UxSLqBK
Instead, I want it to animate like this: https://github.com/Skyscanner/SkyFloatingLabelTextField/blob/master/SkyFloatingLabelTextField/images/example-1.gif

UISearchBar with a white background is impossible?

I really thought it would be easy to set the background color of my UISearchBar's text field to white. But no matter what I try, it always stays offwhite / light gray (#efeff0).
import UIKit
class ViewController: UIViewController {
private let searchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Hello World"
view.backgroundColor = #colorLiteral(red: 0.9588784575, green: 0.9528519511, blue: 0.9350754619, alpha: 1)
searchController.searchBar.searchTextField.backgroundColor = .white
navigationItem.searchController = searchController
}
}
How can I make the search bar have a pure white background color? App is iOS 13+, if that helps.
Tiny test project: https://github.com/kevinrenskers/WhiteSearch.
It's possible. Set the background of the search field with a white image.
let size = CGSize(width: searchController.searchBar.frame.size.width - 12, height: searchController.searchBar.frame.size.height - 12)
let backgroundImage = createWhiteBG(size)!
let imageWithCorner = backgroundImage.createImageWithRoundBorder(cornerRadiuos: 10)!
searchController.searchBar.setSearchFieldBackgroundImage(imageWithCorner, for: UIControl.State.normal)
If you don't want to input an image to app. Try this for create one programmatically.
func createWhiteBG(_ frame : CGSize) -> UIImage? {
var rect = CGRect(x: 0, y: 0, width: 0, height: 0)
rect.size = frame
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.white.cgColor)
context?.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
extension UIImage {
func createImageWithRoundBorder(cornerRadiuos : CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, scale)
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
let context = UIGraphicsGetCurrentContext()
let path = UIBezierPath(
roundedRect: rect,
cornerRadius: cornerRadiuos
)
context?.beginPath()
context?.addPath(path.cgPath)
context?.closePath()
context?.clip()
self.draw(at: CGPoint.zero)
context?.restoreGState()
path.lineWidth = 1.5
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Try this ... Change colors and images according to your preference
DispatchQueue.main.async {
searchBar.backgroundImage = UIImage()
for s in searchBar.subviews[0].subviews {
if s is UITextField {
s.layer.borderWidth = 1.0
s.layer.borderColor = UIColor.lightGray.cgColor
}
}
let searchTextField:UITextField = searchBar.subviews[0].subviews.last as? UITextField ?? UITextField()
searchTextField.layer.cornerRadius = 10
searchTextField.textAlignment = NSTextAlignment.left
let image:UIImage = UIImage(named: "search")!
let imageView:UIImageView = UIImageView.init(image: image)
searchTextField.leftView = nil
searchTextField.placeholder = "Search..."
searchTextField.font = UIFont.textFieldText
searchTextField.rightView = imageView
searchTextField.rightViewMode = UITextField.ViewMode.always
}
Here is My complete Custom Search Bar Which you can define the searchbar backgroundColor and TextField background Color
Tested
import Foundation
class SearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
makeUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
makeUI()
}
private func makeUI( ) {
//SearchBar BackgroundColor
self.backgroundImage = UIImage(color: UIColor.white)
//Border Width
self.layer.borderWidth = 1
//Border Color
self.layer.borderColor = UIColor("DEDEDE")?.cgColor
//Corner Radius
self.layer.cornerRadius = 3
self.layer.masksToBounds = true
//Change Icon
self.setImage(UIImage(named: "search")?
.byResize(to: CGSize(width: 30, height: 30)), for: .search, state: .normal)
if let searchTextField = self.value(forKey: "searchField") as? UISearchTextField {
//TextField Background !!!!!
searchTextField.backgroundColor = UIColor.white
//TextField Font
searchTextField.font = UIFont(name: "Poppins-Regular", size: 21)
searchTextField.textColor = .black
}
}
}

Didn't tapped on textfield

I create programmatically custom textfield
import UIKit
class SearchTextField: UITextField, UITextFieldDelegate {
let padding = UIEdgeInsets(top: 0, left: 40, bottom: 0, right: 5);
init(frame: CGRect, tintText: String, tintFont: UIFont, tintTextColor: UIColor) {
super.init(frame:frame)
self.frame = frame
delegate = self
backgroundColor = .white
textColor = tintTextColor
placeholder = tintText
font = tintFont
createBorder()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delegate = self
}
func createBorder() {
self.layer.cornerRadius = 6
self.layer.borderColor = UIColor(red: 169/255, green: 169/255, blue: 169/255, alpha: 1).cgColor
self.layer.borderWidth = 1
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds, padding)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds, padding)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds, padding)
}
}
and add it like a subview to my view which is a subview of Google maps view
import UIKit
import GoogleMaps
class MapViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var mapView: GMSMapView!
var customSearchBar: SearchTextField!
let searchBarTextColor = UIColor(red: 206, green: 206, blue: 206, alpha: 1)
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: 55.75, longitude: 37.62, zoom: 13.0)
mapView.camera = camera
mapView.isUserInteractionEnabled = true
addTopBarView(mapView: mapView)
}
func addTopBarView(mapView: GMSMapView) {
//heigt of topBar is 14% of height of view^ width is the same
let topBarFrame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height * 0.14)
let topBarView = UIView(frame: topBarFrame)
addTopBarViewBackground(view: topBarView)
addTitleForTopBarView(view: topBarView)
addProfileIconForTopBarView(view: topBarView)
addSettingsIconForTopBarView(view: topBarView)
addSearchBar(view: topBarView)
topBarView.isUserInteractionEnabled = true
mapView.addSubview(topBarView)
}
func addSearchBar(view: UIView) {
let frameCustomSearchBar = CGRect(x: 10, y: 45, width: view.frame.width - 20, height: 40)
let fontCustomSearchBar = UIFont(name: "HelveticaNeueCyr", size: 28) ?? UIFont.italicSystemFont(ofSize: 14)
let textColorCustomSearchBar = UIColor(red: 206/255, green: 206/255, blue: 206/255, alpha: 1)
customSearchBar = SearchTextField(frame: frameCustomSearchBar, tintText: NSLocalizedString("find_petrole", comment: ""), tintFont: fontCustomSearchBar, tintTextColor: textColorCustomSearchBar)
customSearchBar.delegate = self
customSearchBar.isUserInteractionEnabled = true
customSearchBar.isEnabled = true
let iconPinView = UIImageView(image: #imageLiteral(resourceName: "icon_pin"))
iconPinView.frame = CGRect(x: 10, y: 10, width: 12, height: 20)
customSearchBar.addSubview(iconPinView)
let iconAddView = UIImageView(image: #imageLiteral(resourceName: "icon_add"))
iconAddView.frame = CGRect(x: customSearchBar.frame.width - 34, y: 10, width: 20, height: 20)
customSearchBar.addSubview(iconAddView)
view.addSubview(customSearchBar)
}
The textfield(customSearchBar) i see but it doesn't clickable, when i tapped on it nothing happens. I saw a few such problems here but did not find anything that help me.
You need inspect UIView Hierarchy using View Debugging feature of xcode and you need to check that textfield does not overlap with other view.
Run the app. View Debugging works in the simulator and on devices, but it's important to note that it needs to be an iOS 8 simulator or device. That said, you may allow earlier deployment targets in your project, just make sure you run on iOS 8 when you try View Debugging.
Navigate to the screen/view that you want to inspect within the running app.
In the Navigators Panel (left column), select the Debug Navigator (sixth tab). Next to your process, you'll see two buttons – press the rightmost button and select View UI Hierarchy
I guess it's because you put the UITextField under other touchable views so the touch event was intercepted.
if you make the custom textField hierarchy by a non-defalut isUserInteractionEnabled object, remember to enable it.

Adding a UIControl into title of Navbar Programmatically - Swift

I'm trying to add a custom UI Segmented control I created into my root view controller's navbar. Here's my code:
Segmented Control:
#IBDesignable class FeedViewSC: UIControl {
fileprivate var labels = [UILabel]()
var thumbView = UIView()
var items: [String] = ["Tab1", "Tab2"] {
didSet {
setupLabels()
}
}
var selectedIndex : Int = 0 {
didSet{
displayNewSelectedIndex()
}
}
#IBInspectable var font : UIFont! = UIFont.systemFont(ofSize: 13) {
didSet {
setFont()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView() {
layer.cornerRadius = 2
layer.borderColor = UIColor(red: 2/255, green: 239/255, blue: 23/255, alpha: 1).cgColor
backgroundColor = UIColor(red: 239/255, green: 29/255, blue: 239/255, alpha: 1)
setupLabels()
insertSubview(thumbView, at: 0)
}
func setupLabels() {
for label in labels {
label.removeFromSuperview()
}
labels.removeAll(keepingCapacity: true)
for index in 1...items.count {
let label = UILabel(frame: CGRect.zero)
label.text = items[index-1]
label.textAlignment = .center
label.font = UIFont(name: "timesnr",size: 17)
label.textColor = UIColor(red: 51/255, green: 51/255, blue: 51/255, alpha: 1)
self.addSubview(label)
labels.append(label)
}
}
override func layoutSubviews() {
super.layoutSubviews()
var selectFrame = self.bounds
let newWidth = selectFrame.width / CGFloat(items.count)
selectFrame.size.width = newWidth
thumbView.frame = selectFrame
thumbView.backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 1)
thumbView.layer.cornerRadius = 5
let labelHeight = self.bounds.height
let labelWidth = self.bounds.width / CGFloat(labels.count)
for index in 0...labels.count - 1 {
let label = labels[index]
let xPosition = CGFloat(index) * labelWidth
label.frame = CGRect(x: xPosition, y: 0, width: labelWidth, height: labelHeight)
}
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
var calculatedIndex: Int?
for (index, item) in labels.enumerated() {
if item.frame.contains(location){
calculatedIndex = index
}
}
if calculatedIndex != nil {
selectedIndex = calculatedIndex!
sendActions(for: .valueChanged)
}
return false
}
func displayNewSelectedIndex (){
if(self.selectedIndex == -1){
self.selectedIndex = self.items.count-1
}
let label = labels[selectedIndex]
}
func setFont(){
for item in labels {
item.font = font
}
}
}
My VC that I would liek to add this Segmented Control to:
class FeedViewController: UIViewController {
let feedViewSC: FeedViewSC = {
let sc = FeedViewSC()
sc.translatesAutoresizingMaskIntoConstraints = false
return sc
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
view.addSubview(feedViewSC)
setupFeedViewSC()
}
func setupFeedViewSC() {
feedViewSC.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 5).isActive = true
feedViewSC.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
feedViewSC.heightAnchor.constraint(equalToConstant: 35).isActive = true
feedViewSC.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 60).isActive = true
feedViewSC.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -60).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
let img = UIImage()
self.navigationController?.navigationBar.shadowImage = img
self.navigationController?.navigationBar.setBackgroundImage(img, for: UIBarMetrics.default)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
If you can tell me how I can add my custom UIControl to my View Controller's Navigation bar title.
If the FeedViewController is the initial view controller of the NavigationController you can do it very simply by
let feedControl = FeedViewSC(frame: (self.navigationController?.navigationBar.bounds)!)
feedControl.autoresizingMask = [.flexibleWidth,.flexibleHeight]
self.navigationController?.navigationBar.addSubview(feedControl)
feedControl.addTarget(self, action: #selector(FeedViewController.changingTab), for: .valueChanged)
At least I don't see a reason that this would not work for getting it in the navigation bar.
Also not part of the question but if you are having any trouble seeing your control in IB might I suggest.
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.cornerRadius = 2
layer.borderColor = UIColor(red: 2/255, green: 239/255, blue: 23/255, alpha: 1).cgColor
backgroundColor = UIColor(red: 239/255, green: 29/255, blue: 239/255, alpha: 1)
setupLabels()
insertSubview(thumbView, at: 0)
}
As for the control itself I did not test it but your events and your handling may be slightly different than value changed I am not sure.
You could also make the navigation bar of the controller a special designable class and never add it in code but you would probably have to get a reference in the viewDidLoad to use it. The designable would look like
import UIKit
#IBDesignable class DesignableNavBar: UINavigationBar {
var feedControl : FeedViewSC!
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView() {
if feedControl == nil{
feedControl = NavControl(frame: self.bounds)
feedControl.autoresizingMask = [.flexibleHeight,.flexibleWidth]
self.addSubview(feedControl)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
}
And then in your controller in say the viewDidLoad you could do this.
if let navController = self.navigationController{
if navController.navigationBar is DesignableNavBar{
let control = (navController.navigationBar as! DesignableNavBar). feedControl
control?.addTarget(self, action: #selector(ViewController.changingTab), for: .valueChanged)
}
}

Resources