I implemented UISearchController and I'm getting the data from a js callback but when I tape in the searchbar it takes some time to put the data so I wanted to know how to implement a loading indicator into the table view result of UISearchController
Wrote this extension in Swift 3, which seems to work pretty well.
extension UISearchBar {
private var textField: UITextField? {
return subviews.first?.subviews.flatMap { $0 as? UITextField }.first
}
private var activityIndicator: UIActivityIndicatorView? {
return textField?.leftView?.subviews.flatMap{ $0 as? UIActivityIndicatorView }.first
}
var isLoading: Bool {
get {
return activityIndicator != nil
} set {
if newValue {
if activityIndicator == nil {
let newActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
newActivityIndicator.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
newActivityIndicator.startAnimating()
newActivityIndicator.backgroundColor = UIColor.white
textField?.leftView?.addSubview(newActivityIndicator)
let leftViewSize = textField?.leftView?.frame.size ?? CGSize.zero
newActivityIndicator.center = CGPoint(x: leftViewSize.width/2, y: leftViewSize.height/2)
}
} else {
activityIndicator?.removeFromSuperview()
}
}
}
}
Loading indicator on search results controller
Updated solution for iOS 13:
extension UISearchBar {
var textField: UITextField? {
if #available(iOS 13.0, *) {
return searchTextField
} else {
if let textField = value(forKey: "searchField") as? UITextField {
return textField
}
return nil
}
}
private var activityIndicator: UIActivityIndicatorView? {
return textField?.leftView?.subviews.compactMap{ $0 as? UIActivityIndicatorView }.first
}
var isLoading: Bool {
get {
return activityIndicator != nil
} set {
if newValue {
if activityIndicator == nil {
let newActivityIndicator = UIActivityIndicatorView(style: .gray)
newActivityIndicator.startAnimating()
if #available(iOS 13.0, *) {
newActivityIndicator.backgroundColor = UIColor.systemGroupedBackground
} else {
newActivityIndicator.backgroundColor = UIColor.groupTableViewBackground
}
newActivityIndicator.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
textField?.leftView?.addSubview(newActivityIndicator)
let leftViewSize = textField?.leftView?.frame.size ?? CGSize.zero
newActivityIndicator.center = CGPoint(x: leftViewSize.width/2, y: leftViewSize.height/2)
}
} else {
activityIndicator?.removeFromSuperview()
}
}
}
}
When you start searching at that time you can create an activity indicator and add it as the subview of UISearchBar using addSubView method.
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
//set your frame for activity indicator
[searchBar addSubview: spinner];
[spinner startAnimating];
When you finish the search remove it from the search bar using removeFromSuperView method.
Please try this one. May be its helpfull to you
SearchBarWithloading.h
#import <UIKit/UIKit.h>
#interface SearchBarWithloading : UISearchBar
- (void)startActivity; // increments startCount and shows activity indicator
- (void)finishActivity; // decrements startCount and hides activity indicator if 0
#end
SearchBarWithloading.m
import "SearchBarWithloading.h"
#interface SearchBarWithloading()
#property(nonatomic) UIActivityIndicatorView *activityIndicatorView;
#property(nonatomic) int startCount;
#end
#implementation SearchBarWithloading
- (void)layoutSubviews {
UITextField *searchField = nil;
for(UIView* view in self.subviews){
if([view isKindOfClass:[UITextField class]]){
searchField= (UITextField *)view;
break;
}
}
if(searchField) {
if (!self.activityIndicatorView) {
UIActivityIndicatorView *taiv = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
taiv.center = CGPointMake(searchField.leftView.bounds.origin.x + searchField.leftView.bounds.size.width/2,
searchField.leftView.bounds.origin.y + searchField.leftView.bounds.size.height/2);
taiv.hidesWhenStopped = YES;
taiv.backgroundColor = [UIColor whiteColor];
self.activityIndicatorView = taiv;
[taiv release];
_startCount = 0;
[searchField.leftView addSubview:self.activityIndicatorView];
}
}
[super layoutSubviews];
}
- (void)startActivity {
self.startCount = self.startCount + 1;
}
- (void)finishActivity {
self.startCount = self.startCount - 1;
}
- (void)setStartCount:(int)startCount {
_startCount = startCount;
if (_startCount > 0)
[self.activityIndicatorView startAnimating];
else {
[self.activityIndicatorView stopAnimating];
}
}
#end
A cleaner solution for iOS 13 and above:
extension UISearchController {
private static let defaultLeftView: UIView = {
UISearchTextField().leftView ?? UIView()
}()
private static let leftViewSize: CGSize = {
UISearchTextField().leftView?.frame.size ?? .zero
}()
private static let loadingLeftView: UIView = {
let view = UIView(frame: .init(origin: .zero, size: leftViewSize))
let activityIndicatorView = UIActivityIndicatorView(style: .medium)
activityIndicatorView.transform = .init(scaleX: 0.9, y: 0.9)
activityIndicatorView.startAnimating()
view.addSubview(activityIndicatorView)
activityIndicatorView.center = CGPoint(x: leftViewSize.width / 2, y: leftViewSize.height / 2)
return view
}()
func startLoading() {
searchBar.searchTextField.leftView = Self.loadingLeftView
}
func stopLoading() {
searchBar.searchTextField.leftView = Self.defaultLeftView
}
}
Related
I am a beginner in swift
I want to customize my UIPageControl on only code
The UIPageControl style I want is:I can change the little dot above and when I switch to it he can change to another style
I found an article that provides a how-to, which can be done using
It's good, but I don't understand how it works
The method is attached for reference by friends who have the same needs
but that's not my real intention
I want to understand how this program works, can someone explain it to me? or provide a more simple and understandable method
I know so much code is because it supports versions below ios14
but I think this part may be omitted in explaining
The reason why it is not removed is to seek better and more complete code.
thank everybody
import UIKit
class CZPageControl: UIPageControl {
var currentImage: UIImage?
var inactiveImage: UIImage?
var currentTintColor: UIColor?
var inactiveTintColor: UIColor?
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = false
if #available(iOS 14.0, *) {
self.allowsContinuousInteraction = false
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var currentPage: Int {
didSet {
updateDots()
}
}
func updateDots() {
guard let currentImage = self.currentImage, let inactiveImage = self.inactiveImage else {
return
}
if #available(iOS 14.0, *) {
guard let dotContentView = findIndicatorContentView() else {
return
}
for (index, view) in dotContentView.subviews.enumerated() {
if view.isKind(of: UIImageView.self) {
self.currentPageIndicatorTintColor = self.currentTintColor
self.pageIndicatorTintColor = self.inactiveTintColor
let indicatorView = view as! UIImageView
indicatorView.image = nil
if index == self.currentPage {
indicatorView.image = currentImage.withRenderingMode(.alwaysTemplate)
} else {
indicatorView.image = inactiveImage.withRenderingMode(.alwaysTemplate)
}
}
}
} else {
for (index, view) in self.subviews.enumerated() {
if let dot = imageViewForSubview(view, currentPage: index) {
var size = CGSize.zero
if index == self.currentPage {
dot.tintColor = self.currentTintColor
dot.image = currentImage.withRenderingMode(.alwaysTemplate)
size = dot.image!.size
} else {
dot.tintColor = self.inactiveTintColor
dot.image = inactiveImage.withRenderingMode(.alwaysTemplate)
size = dot.image!.size
}
if let superview = dot.superview {
let x = (superview.frame.size.width - size.width) / 2.0
let y = (superview.frame.size.height - size.height) / 2.0
dot.frame = CGRect(x: x, y: y, width: size.width, height: size.height)
}
}
}
}
}
// iOS 14之前创建UIImageView使用
func imageViewForSubview(_ view: UIView, currentPage: Int) -> UIImageView? {
var dot: UIImageView?
if view.isKind(of: UIView.self) {
for subview in view.subviews {
if subview.isKind(of: UIImageView.self) {
dot = (subview as! UIImageView)
break
}
}
if dot == nil {
dot = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
view.addSubview(dot!)
}
} else {
dot = (view as! UIImageView)
}
return dot
}
#available(iOS 14.0, *)
func findIndicatorContentView() -> UIView? {
for contentView in self.subviews {
if let contentViewClass = NSClassFromString("_UIPageControlContentView"), contentView.isKind(of: contentViewClass) {
for indicatorContentView in contentView.subviews {
if let indicatorContentViewClass = NSClassFromString("_UIPageControlIndicatorContentView"), indicatorContentView.isKind(of: indicatorContentViewClass) {
return indicatorContentView
}
}
}
}
return nil
}
}
You will have to call "updateDots()" in viewDidAppear() and your valueChanged handler for the page control.
import UIKit
class CustomImagePageControl: UIPageControl {
let activeImage:UIImage = UIImage(named: "SelectedPage")!
let inactiveImage:UIImage = UIImage(named: "UnselectedPage")!
override func awakeFromNib() {
super.awakeFromNib()
self.pageIndicatorTintColor = UIColor.clear
self.currentPageIndicatorTintColor = UIColor.clear
self.clipsToBounds = false
}
func updateDots() {
var i = 0
for view in self.subviews {
if let imageView = self.imageForSubview(view) {
if i == self.currentPage {
imageView.image = self.activeImage
} else {
imageView.image = self.inactiveImage
}
i = i + 1
} else {
var dotImage = self.inactiveImage
if i == self.currentPage {
dotImage = self.activeImage
}
view.clipsToBounds = false
view.addSubview(UIImageView(image:dotImage))
i = i + 1
}
}
}
fileprivate func imageForSubview(_ view:UIView) -> UIImageView? {
var dot:UIImageView?
if let dotImageView = view as? UIImageView {
dot = dotImageView
} else {
for foundView in view.subviews {
if let imageView = foundView as? UIImageView {
dot = imageView
break
}
}
}
return dot
}
}
I am working on creating a custom control once user presses a button and action completes. I'm trying to replicate behavior of apple music app when album is added it shows confirmation view in center with a check mark as shown below. Are there any similar cocoa controls available to use?
(swift)
Create a singleton class
class CustomView: UIView {
class var sharedView : CustomView {
struct Static {
static var instance : CustomView?
static var token : dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = CustomView()
}
return Static.instance!
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func showInView(view:UIWindow) {
var image = UIImage(named:"SomeImage")
self.frame = view.frame
var originX = view.center.x
var originY = view.center.y
let centerView = UIImageView()
centerView.center = CGPointMake(originX, originY)
centerView.contentMode = UIViewContentMode.Center
centerView.image = image
centerView.alpha = 0
self.addSubview(centerView)
view.addSubview(self)
UIView.animateWithDuration(1, animations: { () -> Void in
centerView.alpha = 1
}) { (_) -> Void in
UIView.animateWithDuration(1, animations: { () -> Void in
centerView.frame.size = CGSizeMake(0,0)
centerView.alpha = 0
}) { (_) -> Void in
self.hide()
}
}
}
func hide()
{
if self.superview != nil
{
self.removeFromSuperview()
}
}
}
In your viewController you can just call the method CustomView.sharedView.showInView(view:UIApplication.sharedApplication.keyWindow())
Objective c .h
#import <UIKit/UIKit.h>
#interface CustomView : UIView
+ (instancetype)sharedInstance;
-(void)showInView:(UIWindow*)view;
#end
objective c .m
#import "CustomView.h"
#implementation CustomView
+ (instancetype)sharedInstance
{
static CustomView *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[CustomView alloc] init];
});
return sharedInstance;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(void)showInView:(UIWindow*)view {
UIImage *image = [UIImage imageNamed:#"img.png"];
self.frame = view.frame;
CGFloat originX = view.center.x;
CGFloat originY = view.center.y;
UIImageView *centerView = [UIImageView new];
centerView.center = CGPointMake(originX, originY);
centerView.contentMode = UIViewContentModeCenter;
centerView.image = image;
centerView.alpha = 0;
[self addSubview:centerView];
[view addSubview:self];
[UIView animateWithDuration:1 animations:^{
centerView.alpha = 1;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1 animations:^{
centerView.frame = CGRectMake(originX, originY, 0, 0);
centerView.alpha = 0;
} completion:^(BOOL finished) {
[self hideView];
}];
}];
}
-(void)hideView {
if(self.superview) {
[self removeFromSuperview];
}
}
#end
Import CustomView.h in your file and
[[CustomView sharedInstance] showInView:[[UIApplication sharedApplication]keyWindow]];
When I switch between my tabs it loads some seconds and I want to know that my data is loading. For that I decided to add an activity indicator.
I wrote a little function:
func showActivityIndicator() {
dispatch_async(dispatch_get_main_queue()) {
self.spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
self.spinner.frame = CGRect(x: 0.0, y: 0.0, width: 80.0, height: 80.0)
self.spinner.center = CGPoint(x:self.loadingView.bounds.size.width / 2, y:self.loadingView.bounds.size.height / 2)
self.loadingView.addSubview(self.spinner)
self.view.addSubview(self.loadingView)
self.spinner.startAnimating()
}
}
that will show my indicator. And try to use it when I tapped from my infoController to button:
#IBAction func goToMainFromInfo(sender: AnyObject) {
self.showActivityIndicator()
self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
self.hideActivityIndicator()
}
}
I show it before segue perform and hide after. It doesn't help me. When I did try to use sync:
#IBAction func goToMainFromInfo(sender: AnyObject) {
dispatch_async(dispatch_get_main_queue()) {
self.showActivityIndicator()
self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
self.hideActivityIndicator()
}
}
But it doesn't help too. When I press to tab it opacity becomes 0.5 and I wait while it loading. But I do not see my activity indicator.
What is the problem?
Just try this:
var indicator = UIActivityIndicatorView()
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
And where you want to start animating
indicator.startAnimating()
indicator.backgroundColor = UIColor.whiteColor()
For stop:
indicator.stopAnimating()
indicator.hidesWhenStopped = true
Note: Avoid the calling of start and stop at the same time. Just give some conditions.
SWIFT : 4.2
Just try this:
var indicator = UIActivityIndicatorView()
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
And where you want to start animating
activityIndicator()
indicator.startAnimating()
indicator.backgroundColor = .white
For stop:
indicator.stopAnimating()
indicator.hidesWhenStopped = true
Swift 3.0
// UIView Extension
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
public extension UIView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let activityIndicatorView = getAssociatedObject(&ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return activityIndicatorView
} else {
let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicatorView.activityIndicatorViewStyle = .gray
activityIndicatorView.color = .gray
activityIndicatorView.center = center
activityIndicatorView.hidesWhenStopped = true
addSubview(activityIndicatorView)
setAssociatedObject(activityIndicatorView, associativeKey: &ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return activityIndicatorView
}
}
set {
addSubview(newValue)
setAssociatedObject(newValue, associativeKey:&ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
// NSObject Extension
public extension NSObject {
func setAssociatedObject(_ value: AnyObject?, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) {
if let valueAsAnyObject = value {
objc_setAssociatedObject(self, associativeKey, valueAsAnyObject, policy)
}
}
func getAssociatedObject(_ associativeKey: UnsafeRawPointer) -> Any? {
guard let valueAsType = objc_getAssociatedObject(self, associativeKey) else {
return nil
}
return valueAsType
}
}
start animation
tableView.activityIndicatorView.startAnimating()
stop animation
tableView.activityIndicatorView.stopAnimating()
You can find more code in Magic
Swift 2+
class ViewController: UITableViewController {
weak var activityIndicatorView: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
tableView.backgroundView = activityIndicatorView
self.activityIndicatorView = activityIndicatorView
activityIndicatorView.startAnimating()
}
...
}
I use two extension methods to add an UIActivityIndicatorView as the backgroundView of the tableview.
extension UITableView {
func showActivityIndicator() {
DispatchQueue.main.async {
let activityView = UIActivityIndicatorView(style: .medium)
self.backgroundView = activityView
activityView.startAnimating()
}
}
func hideActivityIndicator() {
DispatchQueue.main.async {
self.backgroundView = nil
}
}
}
You can show/hide it like this.
tableView.showActivityIndicator()
tableView.hideActivityIndicator()
This code can help you :)
let indicator:UIActivityIndicatorView = UIActivityIndicatorView (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
indicator.color = UIColor .magentaColor()
indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubviewToFront(self.view)
indicator.startAnimating()
SWIFT
Place this below your class:
let indicator:UIActivityIndicatorView = UIActivityIndicatorView (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
Place this in your loadView():
indicator.color = UIColor .magentaColor()
indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubviewToFront(self.view)
indicator.startAnimating()
In my case, I am requesting json objects through a func request, so I placed this at the end of that function to remove the activity indicator once the data loads:
self.indicator.stopAnimating()
self.indicator.hidesWhenStopped = true
Another approach, In my code I added an extension for UITableView (Swift 2.3) :
extension UITableView {
func activityIndicator(center: CGPoint = CGPointZero, loadingInProgress: Bool) {
let tag = 12093
if loadingInProgress {
var indicator = UIActivityIndicatorView()
indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
indicator.tag = tag
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
indicator.color = //Your color here
indicator.center = center
indicator.startAnimating()
indicator.hidesWhenStopped = true
self.superview?.addSubview(indicator)
}else {
if let indicator = self.superview?.viewWithTag(tag) as? UIActivityIndicatorView { {
indicator.stopAnimating()
indicator.removeFromSuperview()
}
}
}
}
Note : My tableview is embedded in a UIView (superview)
Update Swift 4.2:
1.call the activityIndicator function on viewDidLoad
eg:
var indicator = UIActivityIndicatorView()
override func viewDidLoad() {
//Initializing the Activity Indicator
activityIndicator()
//Starting the Activity Indicator
indicator.startAnimating()
//Call Your WebService or API
callAPI()
}
Here is the Code For Adding ActivityIndicator as Subview
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.whiteLarge
indicator.color = .red
indicator.center = self.view.center
self.view.addSubview(indicator)
}
2. Do UI related Operations or API Call and stop activity indicator
func callAPI() {
YourModelClass.fetchResult(someParams,
completionHandler: { (response) in
//Do necessary UIUpdates
//And stop Activity Indicator
self.indicator.stopAnimating()
})
}
func setupSpinner(){
spinner = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height:40))
spinner.color = UIColor(Colors.Accent)
self.spinner.center = CGPoint(x:UIScreen.main.bounds.size.width / 2, y:UIScreen.main.bounds.size.height / 2)
self.view.addSubview(spinner)
spinner.hidesWhenStopped = true
}
Using "lazy var". It's better than function
fileprivate lazy var activityIndicatorView: UIActivityIndicatorView = {
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicatorView.hidesWhenStopped = true
// Set Center
var center = self.view.center
if let navigationBarFrame = self.navigationController?.navigationBar.frame {
center.y -= (navigationBarFrame.origin.y + navigationBarFrame.size.height)
}
activityIndicatorView.center = center
self.view.addSubview(activityIndicatorView)
return activityIndicatorView
}()
Just start the spinner anywhere
like this
func requestData() {
// Request something
activityIndicatorView.startAnimating()
}
#brocolli's answer for swift 4.0. You have to use objc_ before getting or setting associated objects. According to the documentation, The APIs of getting and setting the associated object in Swift are:
func objc_getAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>
) -> AnyObject!
func objc_setAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>,
value: AnyObject!,
policy: objc_AssociationPolicy)
Implementation:
import UIKit
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
extension UIView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let activityIndicatorView = objc_getAssociatedObject(self, &ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return activityIndicatorView
} else {
let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
activityIndicatorView.activityIndicatorViewStyle = .gray
activityIndicatorView.color = .gray
activityIndicatorView.center = center
activityIndicatorView.hidesWhenStopped = true
addSubview(activityIndicatorView)
objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, activityIndicatorView, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return activityIndicatorView
}
}
set {
addSubview(newValue)
objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
In order to place the UIActivityIndicator in foreground, even over the eventual UITableViewController separators, I have adopted this solution.
I have add the UIActivityIndicator programmatically, and add it as a subview of my UINavigationController
var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
// Code
// .... omissis
// Set activity indicator
activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator.color = UIColor.darkGray
activityIndicator.center = tableView.center
activityIndicator.hidesWhenStopped = true
activityIndicator.stopAnimating()
self.navigationController?.view.addSubview(activityIndicator)
}
I have just start & stop it when needed (in my case):
func animateActivityIndicator(_ sender: Any ) {
guard let vc = sender as? UIViewController else { return }
if let v = vc as? MyTableViewController {
if v.activityIndicator.isAnimating {
v.activityIndicator.stopAnimating()
} else {
v.activityIndicator.startAnimating()
}
}
// Others UIViewController or UITableViewController follows...
// all of them exhibits an activityIndicator variable
// implemented programmatically or with the storyboard
}
PS. My environment is Xcode 10.0, iOS 12.0
I have a tableview, where sometimes there might not be any results to list, so I would like to put something up that says "no results" if there are no results (either a label or one table view cell?).
Is there an easiest way to do this?
I would try a label behind the tableview then hide one of the two based on the results, but since I'm working with a TableViewController and not a normal ViewController I'm not sure how smart or doable that is.
I'm also using Parse and subclassing as a PFQueryTableViewController:
#interface TableViewController : PFQueryTableViewController
I can provide any additional details needed, just let me know!
TableViewController Scene in Storyboard:
EDIT: Per Midhun MP, here's the code I'm using
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger numOfSections = 0;
if ([self.stringArray count] > 0)
{
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
numOfSections = 1;
//yourTableView.backgroundView = nil;
self.tableView.backgroundView = nil;
}
else
{
UILabel *noDataLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height)];
noDataLabel.text = #"No data available";
noDataLabel.textColor = [UIColor blackColor];
noDataLabel.textAlignment = NSTextAlignmentCenter;
//yourTableView.backgroundView = noDataLabel;
//yourTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.backgroundView = noDataLabel;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
return numOfSections;
}
And here's the View I'm getting, it still has separator lines. I get the feeling that this is some small change, but I'm not sure why separator lines are showing up?
You can easily achieve that by using backgroundView property of UITableView.
Objective C:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger numOfSections = 0;
if (youHaveData)
{
yourTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
numOfSections = 1;
yourTableView.backgroundView = nil;
}
else
{
UILabel *noDataLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, yourTableView.bounds.size.width, yourTableView.bounds.size.height)];
noDataLabel.text = #"No data available";
noDataLabel.textColor = [UIColor blackColor];
noDataLabel.textAlignment = NSTextAlignmentCenter;
yourTableView.backgroundView = noDataLabel;
yourTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
return numOfSections;
}
Swift:
func numberOfSections(in tableView: UITableView) -> Int
{
var numOfSections: Int = 0
if youHaveData
{
tableView.separatorStyle = .singleLine
numOfSections = 1
tableView.backgroundView = nil
}
else
{
let noDataLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
noDataLabel.text = "No data available"
noDataLabel.textColor = UIColor.black
noDataLabel.textAlignment = .center
tableView.backgroundView = noDataLabel
tableView.separatorStyle = .none
}
return numOfSections
}
Reference UITableView Class Reference
backgroundView Property
The background view of the table view.
Declaration
Swift
var backgroundView: UIView?
Objective-C
#property(nonatomic, readwrite, retain) UIView *backgroundView
Discussion
A table view’s background view is automatically resized to match the
size of the table view. This view is placed as a subview of the table
view behind all cells, header views, and footer views.
You must set this property to nil to set the background color of the
table view.
For Xcode 8.3.2 - Swift 3.1
Here is a not-so-well-known but incredibly easy way to achieve adding a "No Items" view to an empty table view that goes back to Xcode 7. I'll leave it to you control that logic that adds/removes the view to the table's background view, but here is the flow for and Xcode (8.3.2) storyboard:
Select the scene in the Storyboard that has your table view.
Drag an empty UIView to the "Scene Dock" of that scene
Add a UILabel and any constraints to the new view and then create an IBOutlet for that view
Assign that view to the tableView.backgroundView
Behold the magic!
Ultimately this works anytime you want to add a simple view to your view controller that you don't necessarily want to be displayed immediately, but that you also don't want to hand code.
Swift Version of above code :-
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
var numOfSection: NSInteger = 0
if CCompanyLogoImage.count > 0 {
self.tableView.backgroundView = nil
numOfSection = 1
} else {
var noDataLabel: UILabel = UILabel(frame: CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height))
noDataLabel.text = "No Data Available"
noDataLabel.textColor = UIColor(red: 22.0/255.0, green: 106.0/255.0, blue: 176.0/255.0, alpha: 1.0)
noDataLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = noDataLabel
}
return numOfSection
}
But If you are loading Information From a JSON , you need to check whether the JSON is empty or not , therefor if you put code like this it initially shows "No data" Message then disappear. Because after the table reload data the message hide. So You can put this code where load JSON data to an array. SO :-
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func extract_json(data:NSData) {
var error: NSError?
let jsonData: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers , error: &error)
if (error == nil) {
if let jobs_list = jsonData as? NSArray
{
if jobs_list.count == 0 {
var noDataLabel: UILabel = UILabel(frame: CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height))
noDataLabel.text = "No Jobs Available"
noDataLabel.textColor = UIColor(red: 22.0/255.0, green: 106.0/255.0, blue: 176.0/255.0, alpha: 1.0)
noDataLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = noDataLabel
}
for (var i = 0; i < jobs_list.count ; i++ )
{
if let jobs_obj = jobs_list[i] as? NSDictionary
{
if let vacancy_title = jobs_obj["VacancyTitle"] as? String
{
CJobTitle.append(vacancy_title)
if let vacancy_job_type = jobs_obj["VacancyJobType"] as? String
{
CJobType.append(vacancy_job_type)
if let company_name = jobs_obj["EmployerCompanyName"] as? String
{
CCompany.append(company_name)
if let company_logo_url = jobs_obj["EmployerCompanyLogo"] as? String
{
//CCompanyLogo.append("http://google.com" + company_logo_url)
let url = NSURL(string: "http://google.com" + company_logo_url )
let data = NSData(contentsOfURL:url!)
if data != nil {
CCompanyLogoImage.append(UIImage(data: data!)!)
}
if let vacancy_id = jobs_obj["VacancyID"] as? String
{
CVacancyId.append(vacancy_id)
}
}
}
}
}
}
}
}
}
do_table_refresh();
}
func do_table_refresh() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
return
})
}
You can try this control. Its is pretty neat. DZNEmptyDataSet
Or if I were you all I would do is
Check to see if your data array is empty
If it is empty then add one object called #"No Data" to it
Display that string in cell.textLabel.text
Easy peasy
Swift 3 (updated):
override func numberOfSections(in tableView: UITableView) -> Int {
if myArray.count > 0 {
self.tableView.backgroundView = nil
self.tableView.separatorStyle = .singleLine
return 1
}
let rect = CGRect(x: 0,
y: 0,
width: self.tableView.bounds.size.width,
height: self.tableView.bounds.size.height)
let noDataLabel: UILabel = UILabel(frame: rect)
noDataLabel.text = "Custom message."
noDataLabel.textColor = UIColor.white
noDataLabel.textAlignment = NSTextAlignment.center
self.tableView.backgroundView = noDataLabel
self.tableView.separatorStyle = .none
return 0
}
Swift3.0
I hope it server your purpose......
In your UITableViewController .
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != "" {
if filteredContacts.count > 0 {
self.tableView.backgroundView = .none;
return filteredContacts.count
} else {
Helper.EmptyMessage(message: ConstantMap.NO_CONTACT_FOUND, viewController: self)
return 0
}
} else {
if contacts.count > 0 {
self.tableView.backgroundView = .none;
return contacts.count
} else {
Helper.EmptyMessage(message: ConstantMap.NO_CONTACT_FOUND, viewController: self)
return 0
}
}
}
Helper Class with function :
/* Description: This function generate alert dialog for empty message by passing message and
associated viewcontroller for that function
- Parameters:
- message: message that require for empty alert message
- viewController: selected viewcontroller at that time
*/
static func EmptyMessage(message:String, viewController:UITableViewController) {
let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: viewController.view.bounds.size.width, height: viewController.view.bounds.size.height))
messageLabel.text = message
let bubbleColor = UIColor(red: CGFloat(57)/255, green: CGFloat(81)/255, blue: CGFloat(104)/255, alpha :1)
messageLabel.textColor = bubbleColor
messageLabel.numberOfLines = 0;
messageLabel.textAlignment = .center;
messageLabel.font = UIFont(name: "TrebuchetMS", size: 18)
messageLabel.sizeToFit()
viewController.tableView.backgroundView = messageLabel;
viewController.tableView.separatorStyle = .none;
}
I think the most elegant way to solve your problem is switching from a UITableViewController to a UIViewController that contains a UITableView. This way you can add whatever UIView you want as subviews of the main view.
I wouldn't recommend using a UITableViewCell to do this you might need to add additional things in the future and things can quicky get ugly.
You can also do something like this, but this isn't the best solution either.
UIWindow* window = [[UIApplication sharedApplication] keyWindow];
[window addSubview: OverlayView];
Use this code in Your numberOfSectionsInTableView method:-
if ([array count]==0
{
UILabel *fromLabel = [[UILabel alloc]initWithFrame:CGRectMake(50, self.view.frame.size.height/2, 300, 60)];
fromLabel.text =#"No Result";
fromLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
fromLabel.backgroundColor = [UIColor clearColor];
fromLabel.textColor = [UIColor lightGrayColor];
fromLabel.textAlignment = NSTextAlignmentLeft;
[fromLabel setFont:[UIFont fontWithName:Embrima size:30.0f]];
[self.view addSubview:fromLabel];
[self.tblView setHidden:YES];
}
I would present a an overlay view that has the look and message you want if the tableview has no results. You could do it in ViewDidAppear, so you have the results before showing/not showing the view.
SWIFT 3
let noDataLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
noDataLabel.text = "No data available"
noDataLabel.textColor = UIColor.white
noDataLabel.font = UIFont(name: "Open Sans", size: 15)
noDataLabel.textAlignment = .center
tableView.backgroundView = noDataLabel
tableView.separatorStyle = .none
If you don't use the tableview footer and do not want the tableview to fill up the screen with empty default table cells i would suggest that you set your tableview footer to an empty UIView. I do not know the correct syntax for doing this in obj-c or Swift, but in Xamarin.iOS i would do it like this:
public class ViewController : UIViewController
{
UITableView _table;
public ViewController (IntPtr handle) : base (handle)
{
}
public override void ViewWillAppear(bool animated) {
// Initialize table
_table.TableFooterView = new UIView();
}
}
Above code will result in a tableview without the empty cells
Here is the solution that worked for me.
Add the following code to a new file.
Change your table class to the custom class "MyTableView" from storyboard or .xib
(this will work for the first section only. If you want to customize more, do changes in the MyTableView reloadData() function accordingly for other sections)
public class MyTableView: UITableView {
override public func reloadData() {
super.reloadData()
if self.numberOfRows(inSection: 0) == 0 {
if self.viewWithTag(1111) == nil {
let noDataLabel = UILabel()
noDataLabel.textAlignment = .center
noDataLabel.text = "No Data Available"
noDataLabel.tag = 1111
noDataLabel.center = self.center
self.backgroundView = noDataLabel
}
} else {
if self.viewWithTag(1111) != nil {
self.backgroundView = nil
}
}
}
}
If you want to do this without any code, try this!
Click on your tableView.
Change the style from "plain" to "grouped".
Now when you use ....
tableView.backgroundView = INSERT YOUR LABEL OR VIEW
It will not show the separators!
Add this code in one file and change your collection type to CustomCollectionView
import Foundation
class CustomCollectionView: UICollectionView {
var emptyModel = EmptyMessageModel()
var emptyView: EmptyMessageView?
var showEmptyView: Bool = true
override func reloadData() {
super.reloadData()
emptyView?.removeFromSuperview()
self.backgroundView = nil
if !showEmptyView {
return
}
if numberOfSections < 1 {
let rect = CGRect(x: 0,
y: 0,
width: self.bounds.size.width,
height: self.bounds.size.height)
emptyView = EmptyMessageView()
emptyView?.frame = rect
if let emptyView = emptyView {
// self.addSubview(emptyView)
self.backgroundView = emptyView
}
emptyView?.setView(with: emptyModel)
} else {
emptyView?.removeFromSuperview()
self.backgroundView = nil
}
}
}
class EmptyMessageView: UIView {
#IBOutlet weak var messageLabel: UILabel!
#IBOutlet weak var imageView: UIImageView!
var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "EmptyMessageView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
func setView(with model: EmptyMessageModel) {
messageLabel.text = model.message ?? ""
imageView.image = model.image ?? #imageLiteral(resourceName: "no_notification")
}
}
///////////
class EmptyMessageModel {
var message: String?
var image: UIImage?
init(message: String = "No data available", image: UIImage = #imageLiteral(resourceName: "no_notification")) {
self.message = message
self.image = image
}
}
I have made Android application a few months ago. The Toast class is very useful for me.
I do not need to consider the main Thread and place to show it. Anywhere I can show it and just leave that and it is automatically disappeared.
Toast.makeToast(context, msg, Toast.LENGTH_SHORT).show();
That's it. ^^
What about iPhone? Is there something like the Toast? Just show message and do not need to care about it. It will be automatically disappeared.
I've been writing for Android for a long time and I am missing Toast. I've implemented one. Need code? here you are:
ToastView.h
#import <UIKit/UIKit.h>
#interface ToastView : UIView
#property (strong, nonatomic) NSString *text;
+ (void)showToastInParentView: (UIView *)parentView withText:(NSString *)text withDuaration:(float)duration;
#end
ToastView.m
#import "ToastView.h"
#interface ToastView ()
#property (strong, nonatomic, readonly) UILabel *textLabel;
#end
#implementation ToastView
#synthesize textLabel = _textLabel;
float const ToastHeight = 50.0f;
float const ToastGap = 10.0f;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(UILabel *)textLabel
{
if (!_textLabel) {
_textLabel = [[UILabel alloc] initWithFrame:CGRectMake(5.0, 5.0, self.frame.size.width - 10.0, self.frame.size.height - 10.0)];
_textLabel.backgroundColor = [UIColor clearColor];
_textLabel.textAlignment = NSTextAlignmentCenter;
_textLabel.textColor = [UIColor whiteColor];
_textLabel.numberOfLines = 2;
_textLabel.font = [UIFont systemFontOfSize:13.0];
_textLabel.lineBreakMode = NSLineBreakByCharWrapping;
[self addSubview:_textLabel];
}
return _textLabel;
}
- (void)setText:(NSString *)text
{
_text = text;
self.textLabel.text = text;
}
+ (void)showToastInParentView: (UIView *)parentView withText:(NSString *)text withDuaration:(float)duration;
{
//Count toast views are already showing on parent. Made to show several toasts one above another
int toastsAlreadyInParent = 0;
for (UIView *subView in [parentView subviews]) {
if ([subView isKindOfClass:[ToastView class]])
{
toastsAlreadyInParent++;
}
}
CGRect parentFrame = parentView.frame;
float yOrigin = parentFrame.size.height - (70.0 + ToastHeight * toastsAlreadyInParent + ToastGap * toastsAlreadyInParent);
CGRect selfFrame = CGRectMake(parentFrame.origin.x + 20.0, yOrigin, parentFrame.size.width - 40.0, ToastHeight);
ToastView *toast = [[ToastView alloc] initWithFrame:selfFrame];
toast.backgroundColor = [UIColor darkGrayColor];
toast.alpha = 0.0f;
toast.layer.cornerRadius = 4.0;
toast.text = text;
[parentView addSubview:toast];
[UIView animateWithDuration:0.4 animations:^{
toast.alpha = 0.9f;
toast.textLabel.alpha = 0.9f;
}completion:^(BOOL finished) {
if(finished){
}
}];
[toast performSelector:#selector(hideSelf) withObject:nil afterDelay:duration];
}
- (void)hideSelf
{
[UIView animateWithDuration:0.4 animations:^{
self.alpha = 0.0;
self.textLabel.alpha = 0.0;
}completion:^(BOOL finished) {
if(finished){
[self removeFromSuperview];
}
}];
}
#end
Call from ViewController
[ToastView showToastInParentView:self.view withText:#"What a toast!" withDuaration:5.0];
There is no class "out-of-the-box" in UIKit to do this. But it is quite easy to create a class that will offer this behavior.
You just have to create a class that inherit from UIView. This class will have the responsibility
- to create what you want to display,
- to add itself in parent view hierarchy
- to dismiss itself using a timer.
You will be able to use it like :
[ToastView toastViewInView:myParentView withText:#"what a wonderful text"];
Regards,
Quentin
Have not tried but you might want to check:
https://github.com/ecstasy2/toast-notifications-ios
https://github.com/scalessec/Toast
You can do this in many ways one of the way is using UIAlertViewController() in swift3
let alertManager=UIAlertController(title: nil, message: "Welcome!", preferredStyle: .alert)
self.present(alertManager, animated: true, completion: nil)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1,
execute: {
alertManager.dismiss(animated: false, completion: nil)
})
Edit: Updated for Swift 3
Here is a Swift 3 version based on wojciech_maciejewski's answer. This looks more like Android Toast and doesn't stack toasts on each other. It draws toast into the center of the screen. It can handle long multiline texts.
import UIKit
class ToastView: UIView {
private static let hLabelGap: CGFloat = 40.0
private static let vLabelGap: CGFloat = 20.0
private static let hToastGap: CGFloat = 20.0
private static let vToastGap: CGFloat = 10.0
private var textLabel: UILabel!
static func showInParent(_ parentView: UIView, _ text: String, duration: Double = 3.0) {
let labelFrame = CGRect(x: parentView.frame.origin.x + hLabelGap,
y: parentView.frame.origin.y + vLabelGap,
width: parentView.frame.width - 2 * hLabelGap,
height: parentView.frame.height - 2 * vLabelGap)
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 15.0)
label.text = text
label.backgroundColor = UIColor.clear
label.textAlignment = NSTextAlignment.center
label.textColor = UIColor.white
label.numberOfLines = 0
label.frame = labelFrame
label.sizeToFit()
let toast = ToastView()
toast.textLabel = label
toast.addSubview(label)
toast.frame = CGRect(x: label.frame.origin.x - hToastGap,
y: label.frame.origin.y - vToastGap,
width: label.frame.width + 2 * hToastGap,
height: label.frame.height + 2 * vToastGap)
toast.backgroundColor = UIColor.darkGray
toast.alpha = 0.0
toast.layer.cornerRadius = 20.0
toast.center = parentView.center
label.center = CGPoint(x: toast.frame.size.width / 2, y: toast.frame.size.height / 2)
parentView.addSubview(toast)
UIView.animate(withDuration: 0.4, animations: {
toast.alpha = 0.9
label.alpha = 0.9
})
toast.perform(#selector(hideSelf), with: nil, afterDelay: duration)
}
#objc private func hideSelf() {
UIView.animate(withDuration: 0.4, animations: {
self.alpha = 0.0
self.textLabel.alpha = 0.0
}, completion: { t in self.removeFromSuperview() })
}
}
Usage from another controller:
ToastView.showInParent(navigationController!.view, "Hello world")
I'm posting swift version of Scarmysun answer:) many thanks
import Foundation
import UIKit
class ToastView: UIView {
static let toastHeight:CGFloat = 50.0
static let toastGap:CGFloat = 10;
lazy var textLabel: UILabel = UILabel(frame: CGRectMake(5.0, 5.0, self.frame.size.width - 10.0, self.frame.size.height - 10.0))
static func showInParent(parentView: UIView!, withText text: String, forDuration duration: double_t) {
//Count toast views are already showing on parent. Made to show several toasts one above another
var toastsAlreadyInParent = 0;
for view in parentView.subviews {
if (view.isKindOfClass(ToastView)) {
toastsAlreadyInParent++
}
}
var parentFrame = parentView.frame;
var yOrigin = parentFrame.size.height - getDouble(toastsAlreadyInParent)
var selfFrame = CGRectMake(parentFrame.origin.x + 20.0, yOrigin, parentFrame.size.width - 40.0, toastHeight);
var toast = ToastView(frame: selfFrame)
toast.textLabel.backgroundColor = UIColor.clearColor()
toast.textLabel.textAlignment = NSTextAlignment.Center
toast.textLabel.textColor = UIColor.whiteColor()
toast.textLabel.numberOfLines = 2
toast.textLabel.font = UIFont.systemFontOfSize(13.0)
toast.addSubview(toast.textLabel)
toast.backgroundColor = UIColor.darkGrayColor()
toast.alpha = 0.0;
toast.layer.cornerRadius = 4.0;
toast.textLabel.text = text;
parentView.addSubview(toast)
UIView.animateWithDuration(0.4, animations: {
toast.alpha = 0.9
toast.textLabel.alpha = 0.9
})
toast.performSelector(Selector("hideSelf"), withObject: nil, afterDelay: duration)
}
static private func getDouble(toastsAlreadyInParent : Int) -> CGFloat {
return (70.0 + toastHeight * CGFloat(toastsAlreadyInParent) + toastGap * CGFloat(toastsAlreadyInParent));
}
func hideSelf() {
UIView.animateWithDuration(0.4, animations: {
self.alpha = 0.0
self.textLabel.alpha = 0.0
}, completion: { t in self.removeFromSuperview() })
}
}