-
-
Save maysamsh/fd06dd7bb19281229e89116697ebbcb6 to your computer and use it in GitHub Desktop.
// | |
// UISearchBar+Ext.swift | |
// frazeit | |
// | |
// Created by Maysam Shahsavari on 7/30/18. | |
// Updated on 9/26/19. | |
// Copyright © 2018 Maysam Shahsavari. All rights reserved. | |
// Updated: 10/02/2020. | |
import Foundation | |
import UIKit | |
extension UIImage { | |
func imageWithPixelSize(size: CGSize, filledWithColor color: UIColor = UIColor.clear, opaque: Bool = false) -> UIImage? { | |
return imageWithSize(size: size, filledWithColor: color, scale: 1.0, opaque: opaque) | |
} | |
func imageWithSize(size: CGSize, filledWithColor color: UIColor = UIColor.clear, scale: CGFloat = 0.0, opaque: Bool = false) -> UIImage? { | |
let rect = CGRect.init(x: 0, y: 0, width: size.width, height: size.height) | |
UIGraphicsBeginImageContextWithOptions(size, opaque, scale) | |
color.set() | |
UIRectFill(rect) | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image | |
} | |
} | |
extension UISearchBar { | |
private var textField: UITextField? { | |
let subViews = self.subviews.flatMap { $0.subviews } | |
if #available(iOS 13, *) { | |
if let _subViews = subViews.last?.subviews { | |
return (_subViews.first { $0 is UITextField }) as? UITextField | |
} else { | |
return nil | |
} | |
} else { | |
return (subViews.first { $0 is UITextField }) as? UITextField | |
} | |
} | |
private var searchIcon: UIImage? { | |
let subViews = subviews.flatMap { $0.subviews } | |
return ((subViews.first { $0 is UIImageView }) as? UIImageView)?.image | |
} | |
private func getViewElement<T>(type: T.Type) -> T? { | |
let svs = subviews.flatMap { $0.subviews } | |
guard let element = (svs.first { $0 is T }) as? T else { return nil } | |
return element | |
} | |
func getSearchBarTextField() -> UITextField? { | |
return getViewElement(type: UITextField.self) | |
} | |
func setTextColor(color: UIColor) { | |
if let textField = getSearchBarTextField() { | |
textField.textColor = color | |
} | |
} | |
func setTextFieldColor(color: UIColor) { | |
if let textField = getViewElement(type: UITextField.self) { | |
switch searchBarStyle { | |
case .minimal: | |
textField.layer.backgroundColor = color.cgColor | |
textField.layer.cornerRadius = 6 | |
case .prominent, .default: | |
textField.backgroundColor = color | |
@unknown default: | |
break | |
} | |
} | |
} | |
func setPlaceholderTextColor(color: UIColor) { | |
if let textField = getSearchBarTextField() { | |
textField.attributedPlaceholder = NSAttributedString(string: self.placeholder != nil ? self.placeholder! : "", attributes: [NSAttributedString.Key.foregroundColor: color]) | |
} | |
} | |
/////// | |
private var activityIndicator: UIActivityIndicatorView? { | |
return textField?.leftView?.subviews.compactMap{ $0 as? UIActivityIndicatorView }.first | |
} | |
// Public API | |
var isLoading: Bool { | |
get { | |
return activityIndicator != nil | |
} set { | |
let _searchIcon = searchIcon | |
if newValue { | |
if activityIndicator == nil { | |
let _activityIndicator: UIActivityIndicatorView | |
if #available(iOS 13.0, *) { | |
_activityIndicator = UIActivityIndicatorView(style: .medium) | |
} else { | |
_activityIndicator = UIActivityIndicatorView(style: .gray) | |
} | |
_activityIndicator.startAnimating() | |
_activityIndicator.backgroundColor = UIColor.clear | |
let clearImage = UIImage().imageWithPixelSize(size: CGSize.init(width: 14, height: 14)) ?? UIImage() | |
self.setImage(clearImage, for: .search, state: .normal) | |
textField?.leftViewMode = .always | |
textField?.leftView?.addSubview(_activityIndicator) | |
let leftViewSize = CGSize.init(width: 14.0, height: 14.0) | |
_activityIndicator.center = CGPoint(x: leftViewSize.width/2, y: leftViewSize.height/2) | |
} | |
} else { | |
self.setImage(_searchIcon, for: .search, state: .normal) | |
activityIndicator?.removeFromSuperview() | |
} | |
} | |
} | |
} | |
/* | |
Usage: | |
To show the acttivity indicator | |
searchController.searchBar.isLoading = true | |
To stop the activity indicator (assuming it will be called when a network or extensive block is finished) | |
DispatchQueue.main.async { | |
searchController.searchBar.isLoading = false | |
} | |
*/ |
Yes great extension, thank you.
Unfortunately it doesn't work on iOS 10, it shows a square. Do you have an idea to fix it ?
Hello,
My suggestion to support iOS 10 :
var isLoading: Bool {
get {
return activityIndicator != nil
} set {
if newValue {
if activityIndicator == nil {
var style: UIActivityIndicatorView.Style = UIActivityIndicatorView.Style.gray
var backgroundColor: UIColor = UIColor.white
if #available(iOS 11.0, *) {
style = UIActivityIndicatorView.Style.white
backgroundColor = UIColor.clear
}
let _activityIndicator = UIActivityIndicatorView(style: style)
_activityIndicator.startAnimating()
_activityIndicator.backgroundColor = backgroundColor
if #available(iOS 11.0, *) {
self.setImage(UIImage(), for: .search, state: .normal)
}
textField?.leftView?.addSubview(_activityIndicator)
let leftViewSize = textField?.leftView?.frame.size ?? CGSize.zero
_activityIndicator.center = CGPoint(x: leftViewSize.width/2, y: leftViewSize.height/2)
}
} else {
if #available(iOS 11.0, *) {
let _searchIcon = searchIcon
self.setImage(_searchIcon, for: .search, state: .normal)
}
activityIndicator?.removeFromSuperview()
}
}
}
Yes great extension, thank you.
Unfortunately it doesn't work on iOS 10, it shows a square. Do you have an idea to fix it ?
There is a problem in the line self.setImage(_searchIcon, for: .search, state: .normal)
the image coming from it has accessibilityFrame = (0,0,0,0)
if i use a custom image from _searchIcon, for e.g = _searchIcon = UIImage(systemName: "search")
It is working.
Try the new code, It works fine on iOS11 too.
Yes great extension, thank you.
Unfortunately it doesn't work on iOS 10, it shows a square. Do you have an idea to fix it ?There is a problem in the line self.setImage(_searchIcon, for: .search, state: .normal)
the image coming from it has accessibilityFrame = (0,0,0,0)
if i use a custom image from _searchIcon, for e.g = _searchIcon = UIImage(systemName: "search")
It is working.
tushar40 is right. @maysamsh great effort! However there is a bug after setting isLoading to false on main queue. The search bar icon is not showing again and disappears after the indicator removed from the screen.
@onursahindur Have you tried the new code?
@maysamsh yes, I tried on Swift 5 with Xcode 11.2.1, on Simulator running iOS 13.2. However after the search icon replaced with the indicator, the search icon disappears from the left side of search field's text field.
First all, thanks for the gist and the great solution. I will like to suggest some improvements:
To remove the warning on iOS 13 and use the correct style:
if activityIndicator == nil {
let _activityIndicator: UIActivityIndicatorView
if #available(iOS 13.0, *) {
_activityIndicator = UIActivityIndicatorView(style: .medium)
} else {
_activityIndicator = UIActivityIndicatorView(style: .gray)
}
use first instead of filter
private var textField: UITextField? {
let subViews = self.subviews.flatMap { $0.subviews }
if #available(iOS 13, *) {
if let _subViews = subViews.last?.subviews {
return (_subViews.first { $0 is UITextField }) as? UITextField
} else {
return nil
}
} else {
return (subViews.first { $0 is UITextField }) as? UITextField
}
}
private var searchIcon: UIImage? {
let subViews = subviews.flatMap { $0.subviews }
return ((subViews.first { $0 is UIImageView }) as? UIImageView)?.image
}
private func getViewElement<T>(type: T.Type) -> T? {
let svs = subviews.flatMap { $0.subviews }
guard let element = (svs.first { $0 is T }) as? T else { return nil }
return element
}
remove force
private func setPlaceholderTextColor(color: UIColor) {
if let textField = getSearchBarTextField() {
textField.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor: color])
}
}
Great extension, works like a charm!