I want to have an UIProgressView to show a progress on the bottom of a navigation bar (just like when sending an iMessage or text message in iOS 7). But I need this consistently on every table view view of my navigation controller. So for me it was clear: I have to add this to the UINavigationController. But the problem is, it's not possible to add an UIProgressView to the UINavigationController. So I tried out two things:
1st I tried to add it to UINavigationController's view programmatically. But the problem was to position the UIProgressView and to make it look good when changing device rotation.
The 2nd thing I tried is to add the UIProgressView to every UITableView, but then I really have to do this for every view. Also it doesn't look good, because it is not on top of the navigation bar but beneath it. But the main reason why I didn't like the 2nd solution is because the ProgressViews go and come with their TableView, so you don't have a static one but changing ones.
After this, I don't have any idea to do this, so I ask you… does anyone have an idea how to do this?
That's how it should look like:
I reworked the original poster's answer so that the bar is actually just inside the navigation bar. What's nice about this is that when its showing, it overlaps the one pixel bottom line (in effect replacing it), so when you animate the progress bar to hidden, the progress bar fades out and the separator line fades in. The key part of this solution is adding the progress bar to the Navigation Controller's view, not the Navigation bar.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIProgressView *progress = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];;
[self.view addSubview:progress];
UINavigationBar *navBar = [self navigationBar];
#if 1
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[navBar]-0-[progress]"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(progress, navBar)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[progress]|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(progress)]];
#else
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:progress attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:navBar attribute:NSLayoutAttributeBottom multiplier:1 constant:-0.5];
[self.view addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:progress attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:navBar attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
[self.view addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:progress attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:navBar attribute:NSLayoutAttributeRight multiplier:1 constant:0];
[self.view addConstraint:constraint];
#endif
[progress setTranslatesAutoresizingMaskIntoConstraints:NO];
[progress setProgress:0.5 animated:NO];
}
I'm not sure why its necessary to add the 0.5 offset to the NSLayoutContstraints code to get the same match, but it is. I use these not the visual formats, but the choice is yours. Note that contraining to the bottoms makes this seamless in rotation too.
I finally found a solution:
I made a custom UINavigationController and added this to viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
progress = [[UIProgressView alloc] init];
[[self view] addSubview:progress];
UIView *navBar = [self navigationBar];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[navBar]-[progress(2@20)]"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(progress, navBar)]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[progress]|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(progress)]];
[progress setTranslatesAutoresizingMaskIntoConstraints:NO];
[progress setProgress:0 animated:NO];
}
I created a new UIProgressView (I declared in @interface
) added the constraints to position it beneath the navigation bar and (this step is important:) set translatesAutoresizingMaskIntoConstraints
to NO
.
Building on what's already been suggested here, if you want to make it display on every navigation bar in the app, you can make it into an extension (Swift) on UINavigationController:
extension UINavigationController {
public override func viewDidLoad() {
super.viewDidLoad()
let progressView = UIProgressView(progressViewStyle: .Bar)
self.view.addSubview(progressView)
let navBar = self.navigationBar
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[navBar]-0-[progressView]", options: .DirectionLeadingToTrailing, metrics: nil, views: ["progressView" : progressView, "navBar" : navBar]))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[progressView]|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["progressView" : progressView]))
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.setProgress(0.5, animated: false)
}
}
UPDATE (Uses Swift 3 Syntax)
Here is a bit more complete solution. I put this extension into a file called UINavigationController+Progress.swift. (Notice I'm using the UIView
tag property to find the UIProgressView
with the optional progressView
property. There may be more elegant ways to do that, but this seems the most straightforward)
import UIKit
let kProgressViewTag = 10000
let kProgressUpdateNotification = "kProgressUpdateNotification"
extension UINavigationController {
open override func viewDidLoad() {
super.viewDidLoad()
let progressView = UIProgressView(progressViewStyle: .bar)
progressView.tag = kProgressViewTag
self.view.addSubview(progressView)
let navBar = self.navigationBar
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[navBar]-0-[progressView]", options: .directionLeadingToTrailing, metrics: nil, views: ["progressView" : progressView, "navBar" : navBar]))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[progressView]|", options: .directionLeadingToTrailing, metrics: nil, views: ["progressView" : progressView]))
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.setProgress(0.0, animated: false)
NotificationCenter.default.addObserver(self, selector: #selector(UINavigationController.didReceiveNotification(notification:)), name: NSNotification.Name(rawValue: kProgressUpdateNotification), object: nil)
}
var progressView : UIProgressView? {
return self.view.viewWithTag(kProgressViewTag) as? UIProgressView
}
func didReceiveNotification(notification:NSNotification) {
if let progress = notification.object as? ProgressNotification {
if progress.current == progress.total {
self.progressView?.setProgress(0.0, animated: false)
} else {
let perc = Float(progress.current) / Float(progress.total)
self.progressView?.setProgress(perc, animated: true)
}
}
}
}
class ProgressNotification {
var current: Int = 0
var total: Int = 0
}
So I've given a specific implementation here that assumes you want a current count and a total count value to be used to update the progress bar. Now, what you need is to post the notification from the code that is performing whatever tasks are to be used to determine progress--for example downloading a list of files. Here's the code to post the notification:
let notification = ProgressNotification()
notification.current = processedTaskCount
notification.total = totalTaskCount
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: kProgressUpdateNotification), object: notification)
}
You can do this in the root viewController of the navigationController:
override func viewDidLoad() {
super.viewDidLoad()
// if VC is pushed in a navigation controller I add a progress bar
if let navigationVC = self.navigationController {
// create progress bar with .bar style and add it as subview
let progressBar = UIProgressView(progressViewStyle: .Bar)
navigationVC.navigationBar.addSubview(self.progressView)
// create constraints
// NOTE: bottom constraint has 1 as constant value instead of 0; this way the progress bar will look like the one in Safari
let bottomConstraint = NSLayoutConstraint(item: navigationVC.navigationBar, attribute: .Bottom, relatedBy: .Equal, toItem: progressBar, attribute: .Bottom, multiplier: 1, constant: 1)
let leftConstraint = NSLayoutConstraint(item: navigationVC.navigationBar, attribute: .Leading, relatedBy: .Equal, toItem: progressBar, attribute: .Leading, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: navigationVC.navigationBar, attribute: .Trailing, relatedBy: .Equal, toItem: progressBar, attribute: .Trailing, multiplier: 1, constant: 0)
// add constraints
progressBar.translatesAutoresizingMaskIntoConstraints = false
navigationVC.view.addConstraints([bottomConstraint, leftConstraint, rightConstraint])
}
}
But if you want always in the nav bar the same progress bar for all the pushed VCs then it is better to subclass UINavigationController and:
override func viewDidLoad() {
super.viewDidLoad()
// create progress bar with .bar style and keep reference with a property
let progressBar = UIProgressView(progressViewStyle: .Bar)
self.progressBar = progressBar
// add progressBar as subview
self.navigationBar.addSubview(progressBar)
// create constraints
// NOTE: bottom constraint has 1 as constant value instead of 0; this way the progress bar will look like the one in Safari
let bottomConstraint = NSLayoutConstraint(item: self.navigationBar, attribute: .Bottom, relatedBy: .Equal, toItem: progressBar, attribute: .Bottom, multiplier: 1, constant: 1)
let leftConstraint = NSLayoutConstraint(item: self.navigationBar, attribute: .Leading, relatedBy: .Equal, toItem: progressBar, attribute: .Leading, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: self.navigationBar, attribute: .Trailing, relatedBy: .Equal, toItem: progressBar, attribute: .Trailing, multiplier: 1, constant: 0)
// add constraints
progressBar.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraints([bottomConstraint, leftConstraint, rightConstraint])
}
You can add ProgressBar in titleView of UInavigationController as displayed in below screenshot:
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0,0,200,30)];
[customView setBackgroundColor:[UIColor whiteColor]];
UIProgressView *p = [[UIProgressView alloc] initWithFrame:CGRectMake(10, 20, 180,20)]; // Here pass frame as per your requirement
p.progress = 0.5f;
[customView addSubview:p];
self.navigationItem.titleView = customView;
OUTPUT :
Hope it helps you.
I've outlined how to do this in my blog post here
TL;DR
create protocol which presents a UIProgressView as UINavigationController subview
create protocol which updates UIProgressView
- conform your UINavigationController to the presenter protocol
- conform each UIViewController within your navigation stack to the updater protocol
来源:https://stackoverflow.com/questions/19211999/showing-a-uiprogressview-inside-or-on-top-of-a-uinavigationcontrollers-uinaviga