Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A small extension for UISearchBar which shows an UIActivityIndicator while searching
//
// 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.filter { $0 is UITextField }).first as? UITextField
}else{
return nil
}
} else {
return (subViews.filter { $0 is UITextField }).first as? UITextField
}
}
private var searchIcon: UIImage? {
let subViews = subviews.flatMap { $0.subviews }
return ((subViews.filter { $0 is UIImageView }).first as? UIImageView)?.image
}
//////
private func getViewElement<T>(type: T.Type) -> T? {
let svs = subviews.flatMap { $0.subviews }
guard let element = (svs.filter { $0 is T }).first 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(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
}
*/
@johnjcamilleri

This comment has been minimized.

Copy link

johnjcamilleri commented Jan 24, 2019

Great extension, works like a charm!

@antoinepemeja

This comment has been minimized.

Copy link

antoinepemeja commented Mar 19, 2019

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 ?

@antoinepemeja

This comment has been minimized.

Copy link

antoinepemeja commented Mar 20, 2019

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()
            }
        }
    }
@tushar40

This comment has been minimized.

Copy link

tushar40 commented Feb 9, 2020

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.

@maysamsh

This comment has been minimized.

Copy link
Owner Author

maysamsh commented Feb 10, 2020

Try the new code, It works fine on iOS11 too.

@onursahindur

This comment has been minimized.

Copy link

onursahindur commented Feb 12, 2020

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.

@maysamsh

This comment has been minimized.

Copy link
Owner Author

maysamsh commented Feb 12, 2020

@onursahindur Have you tried the new code?

@onursahindur

This comment has been minimized.

Copy link

onursahindur commented Feb 13, 2020

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.