My activity indicator seems not disappear after I go back page. For the first time, it's disappear. Then I go to second view controller then go back to the first view controller. The activity indicator supposedly not to showing up since all UIViews already appeared.
My code
var activityView : UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(0,0, 50, 50)) as UIActivityIndicatorView
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.POST , "192.168.0.1/test", parameters: [])
.responseJSON { response in
if response.result.isSuccess {
var jsonObj = JSON(response.result.value!)
print(jsonObj)
self.activityView.stopAnimating()
}
}
}
override func viewDidAppear(animated: Bool) {
activityView.center = self.view.center
activityView.hidesWhenStopped = true
activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
view.addSubview(activityView)
activityView.startAnimating()
}
You are showing your activityIndicator in viewDidAppear. That is why it appears when you go back from second to first. Also have a look at view life cycle. viewDidLoad is only called once whenever view controller is initialized, but viewDidAppear will be called when ever the view controller is presented. (either back navigation or presented again)
So its better to add views in viewDidLoad and activity indicator should always be associated with its network call. Before starting the call show the activity indicator and hide or remove once its done.
Also, you have missed super calls. Be sure to always call super methods when overriding.
Ex:
override func viewDidLoad() {
super.viewDidLoad()
addActivityIndicator()
fetchRemoteData()
}
func addActivityIndicator() {
activityView.center = self.view.center
activityView.hidesWhenStopped = true
activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
view.addSubview(activityView)
}
func fetchRemoteData() {
activityView.startAnimating()
Alamofire.request(.POST , "192.168.0.1/test", parameters: [])
.responseJSON { response in
if response.result.isSuccess {
var jsonObj = JSON(response.result.value!)
print(jsonObj)
self.activityView.stopAnimating()
}
}
}
U want to hide and show your activity indicator when you want. And also make sure to set as bringSubviewToFront
Related
I have a textview with text in it. It takes a while to load the text, so how can I show a loading icon while it loads the text to it, and when it's done placing the text in the textview then delete the loading icon?
Hope to hear from you guys
Tested code:-
//show progress activity indicator inside UITextView
extension UITextView {
func loadingIndicator(show: Bool) {
if show {
let indicator = UIActivityIndicatorView()
self.isEditable = false
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
indicator.color = UIColor.red
let viewHeight = self.bounds.size.height
let viewWidth = self.bounds.size.width
indicator.center = CGPoint(x: viewWidth/2, y: viewHeight/2)
self.addSubview(indicator)
indicator.startAnimating()
} else {
for view in self.subviews {
if let indicator = view as? UIActivityIndicatorView {
indicator.stopAnimating()
indicator.removeFromSuperview()
self.isEditable = true
}
}
}
}
}
Usage:-
tvTextView.loadingIndicator(show: true/false)
Use UIActivityIndicator :
Add UIActivityIndicator on View
Set its activityIndicator.isHidden = true
Then use activityIndeicator.hidesWhenStopped = true
Set its activityIndicator.isHidden = false
And when you assigning text to TextView then use activityIndicator.startAnimating()
And when text is loaded to TextView then use
activityIndicator.stopAnimating()
Hope it helps you
This is one of those simple sounding things that is actually a little tricky.
First the simple part is to add a UIActivityIndicatorView and set it up appropriately (add to the view, set to hidden initially, the hidesWhenStopped property to true, etc).
Now you would think you could just do something like this:
self.activityIndictor.startAnimating()
self.textView.text = self.data
self.activityIndicator.stopAnimating()
but that won't work because execution waits when setting the text view and doesn't update the UI so you don't see the activity indicator.
What does work is this:
self.activityIndicator.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.textView.text = self.data
self.activityIndicator.stopAnimating()
}
What that does is to start the activity indicator animating and then dispatch the actual setting of the text view back onto the main thread which gives the UI a chance to update and you see the activity indicator.
Note you have to use asyncAfter with a small time delay instead of just a straight async call because that doesn't give the UI time to update.
I have been trying to create a quick method that will allow me to replace any UIView with a spinner right before a process starts and do the re-show the view once my process is done. For some reason, the UIView does disappear but the spinner never shows. These are the methods in question:
func showLoader(view: UIView, controller: UIViewController) -> UIActivityIndicatorView {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
spinner.color = AC_BLUE
spinner.center = view.center
spinner.startAnimating()
view.alpha = 0
controller.view.addSubview(spinner)
return spinner
}
func hideLoader(view: UIView, spinner: UIActivityIndicatorView) {
view.alpha = 1
spinner.removeFromSuperview()
}
..which I call with something like this:
let spinner = Extensions().showLoader(view: signInBtn, controller: self)
APICalls().getUser(email: usernameTextField.text!, pass: passwordTextField.text!) { success, user in
//..CODE..//
Extensions().hideLoader(view: self.signInBtn, spinner: spinner)
}
Also, I tried centering on the main VC view, and that does work. So I'm guessing it must be related to the view's position.
Thanks!
Try setting this before adding the spinner to the controllers view (instead of the old spinner.center = view.center):
spinner.center = view.superview!.convert(view.center, to: controller.view)
You need to convert the view's center to the coordinates of the controller.view.
I have a table in view "A" that is inside a tab bar controller. When I scroll down to reload the table refresh control start animating.When press tab bar controller going in view "B" and later go back to view "A", refresh control is visible but isn't animating.
Can someone help me? thank
func refresh(sender:AnyObject)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
Call API for data
dispatch_async(dispatch_get_main_queue(), { () -> Void in
UPDATE UI
refreshControl.endRefreshing()
})
});
}
override func viewDidAppear(animated: Bool)
{
if(requestTerminated == false)
{
//Continue animate refresh control somehow
}
}
Suppose you have this situation in your controller A , for example in viewDidLoad:
//global var
var refreshControl:UIRefreshControl!
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "pull to refresh")
self.refreshControl.addTarget(self, action: #selector(MyTableViewController.pullToRefresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl)
As can you see you have your refreshControl add as a subview to your A table controller. When you go to another controller probably you present another controller (B) and when you come back to A your refresh is freezed or stucked.
You could start UIRefreshControl animation on viewWillAppear and end it on viewDidDisappear. During transition save the state of refresh process to know when to show UIRefreshControl.
You can use a boolean like this:
// Refresh state
var isRefreshing = false
// Call on viewWillApper
func superviewWillApper(){
if isRefreshing && !refreshControl.refreshing{
startRefreshAnimation()
}
}
// Call on viewDidDisapper
func superviewDidDisappear(){
endRefreshAnimation(false, dataFetched: !isRefreshing)
}
func startRefreshAnimation(){
refreshControl.beginRefreshing()
contentOffset = CGPointMake(0, -refreshControl.bounds.size.height)
isRefreshing = true
}
//Saves state of refresh
func endRefreshAnimation(wasEmpty: Bool, dataFetched: Bool){
refreshControl.endRefreshing()
isRefreshing = !dataFetched
if !wasEmpty{
setContentOffset(CGPointZero, animated: true)
}else{
setContentOffset(CGPointZero, animated: false)
}
}
I wonder how I can show a VC after the remote data has loaded. I am not using a tableView but a normal VC.
My code look like this:
viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
loadItemData(id)
}
func loadItemData(aId: Int) {
Service.getItem(aId) { (JSON) -> () in
self.iData = JSON
self.configureData(self.iData)
}
}
func configureData(iData: JSON) {
if let type = iData["item_type"].int {
if let == 1 {
someButton.hidden = true
}
}
if let title = iData["item_title"].string {
titleLabel.text = title
}
}
What happens is that my VC first loads with the button visible and with my text label containing "dummy text" from storyboard, then when the data has loaded the button will hide and the text label will change.
So my question now is how I can hide my VC or show some loading indicator until the data has loaded.
Also worth saying this is the 2nd view. My apps start with a tableView and when you click on a cell you end up in this VC. So I could also load the data when the cell gets clicked then pass it to this VC.
Using the activity indicator is better, here's how to do it
First make sure you add Activity Indicator in your VC
override func viewDidLoad() {
super.viewDidLoad()
self.myActivityIndicator.startAnimating()
loadItemData(id)
}
func loadItemData(aId: Int) {
Service.getItem(aId) { (JSON) -> () in
self.iData = JSON
self.configureData(self.iData)
self.myActivityIndicator.stopAnimating()
}
}
Rather than try to hide the VC (try to load the data and pass it), it will makes the apps feel not responsive, because you have to wait the data loaded and then the VC will show.
I have a BaseViewController that my UIViewControllers extend so i can have explicit functions that i dont need to rewrite. Something i would like would be a functions such as self.showSpinner() and the viewController would show the spinner
My Code looks like this
class BaseViewController: UIViewController {
var actvIndicator : UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
self.actvIndicator = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
self.actvIndicator.color = UIColor.blackColor()
self.actvIndicator.backgroundColor = UIColor.blackColor()
self.actvIndicator.frame = CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2, 100, 100);
self.actvIndicator.center = self.view.center
self.actvIndicator .startAnimating()
self.view.addSubview(self.actvIndicator)
self.actvIndicator.bringSubviewToFront(self.view)
self.edgesForExtendedLayout = UIRectEdge.None
self.navigationController?.navigationBar.translucent = false
}
func showSpinner(){
self.actvIndicator.startAnimating()
}
func hideSpinner(){
self.actvIndicator.stopAnimating()
}
}
And my viewcontrollers looks like this
class MyProjectViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.showSpinner()
}
}
MyProjectViewController have UITableView that fills the entire screen. When i set tblProjects.alpha = 0 i can see the spinner. But i want it in the front.
i also tried self.view.bringSubviewToFront(self.actvIndicator)
What am i missing?
A couple quick notes before I get into what I think your problem is:
When you add a subview it is automatically added to the top layer, no need for the bringSubviewToFront: in viewDidLoad: (which is being used wrong anyway).
You should not set view frames in viewDidLoad: (e.g. centering a view). Frames are not setup yet, so you should move that to viewWillAppear: or some other variant.
Now your issue is most likely a view hierarchy problem (further confirmed by your comment) and thus can probably be fixed by pushing the spinner to the front every time you want it to be shown, like:
func showSpinner() {
self.view.bringSubviewToFront(self.actvIndicator)
self.actvIndicator.startAnimating()
}
The problem here stands on the fact that table view is draw after you are calling self.view.bringSubviewToFront(self.actvIndicator). A possible workaround for this is to call bringSubviewToFront when showing the spinner
func showSpinner(){
self.view.bringSubviewToFront(self.actvIndicator)
self.actvIndicator.startAnimating()
}