how show activityindicator in swift?

2019-09-14 15:31发布

问题:

in my app i have a login with a usernamaeTextfield, passwordtextfield and login button. When the login button is tapped i check the fields. I need show a activityindicator in this time, but my var activityindicator is always hidden. This is my code.

@IBOutlet weak var activity: UIActivityIndicatorView!
override func viewDidLoad() {
    self.activity.hidden = true
    super.viewDidLoad()}

@IBAction func login(sender: AnyObject) {
    activity.hidden = false
    activity.startAnimating()
    if (self.username.isEmpty || self.password.isEmpty){
            self.showAlert("Asegurese de ingresar un usuario y contraseña!")
        }else{
            var user = user_function()
            if !user.user_valid(self.username,password: self.password){
                self.showAlert("Usuario Invalido")
            }else{

            }
        }

    activity.hidden = true
    activity.stopAnimating()
 }

my code of user_valid is

func user_valid(username :String, password : String)->Bool{
    var resultados : Array<JSON> = []
    userbase64 = self.encode_to_base64(username)
    passbase64 = self.encode_to_base64(password)

    var api = channels_function()

     resultados =  api.load_videos("https://api.cxntv.com/api/v1/videos/?type=canales&page_size=100&ordering=-id")

    if errormessage.isEmpty{
        api.save_LiveChannels(resultados)
        saver_user(userbase64, passbase64: passbase64, username: username, password: password)
        errormessage = ""
        return true

    }else{
        errormessage = ""
        return false}
}

and loads videos is:

func load_videos(url :String)->Array<JSON>{
    var resultados : Array<JSON> = []
    var request = Get_Data()

    self.task_completed = false
    request.remoteUrl = url
    request.getData({data, error -> Void in
        println("los datos")
        //println(data)
        if (data != nil){
            // Fix possible error if no "results" key
            if let results = data["results"].array {
                resultados = results
                self.task_completed = true

            }

            println("Data reloaded")
        } else {
            println("api.getData failed")
            self.task_completed = true
        }


    })
    while(!self.task_completed){}

    return resultados
}

and get data is:

var remoteUrl = ""
func getData(completionHandler: ((JSON!, NSError!) -> Void)!) -> Void {

    let url: NSURL = NSURL(string: remoteUrl)!
    let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
    let session = NSURLSession.sharedSession()
    println(request.HTTPBody)
    request.addValue(userbase64 ,forHTTPHeaderField: "X_CXN_USER")
    request.addValue( passbase64,forHTTPHeaderField: "X_CXN_PASS")
    let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        if (error != nil) {
            return completionHandler(nil, error)
        }
        var error: NSError?
        let json = JSON(data : data)
        if (error != nil){
            return completionHandler(nil, error)
        } else {
            if let results = json["detail"].string {
                errormessage = results
                return completionHandler(nil, error)
            } else {
                return completionHandler(json, nil)
            }
        }
    })

    task.resume()

}

回答1:

The problem is loadVideos, in which you've taken a very nice, asynchronous method and made it synchronous (blocking UI updates), and, have done so in a pretty inefficient manner with a spinning while loop. Instead, make loadVideos asynchronous:

func loadVideos(url:String, completionHandler: (Array<JSON>?) -> ()) {
    var request = Get_Data()

    request.remoteUrl = url
    request.getData {data, error in
        println("los datos")
        //println(data)
        if (data != nil){
            // Fix possible error if no "results" key
            if let results = data["results"].array {
                completionHandler(results)
            }

            println("Data reloaded")
        } else {
            println("api.getData failed")
            completionHandler(nil)
        }
    }
}

And then userValid should use the completion block parameter (employing a completionBlock pattern of its own):

func userValid(username :String, password : String, completionHandler: (Bool) -> ()) {
    userbase64 = encode_to_base64(username)
    passbase64 = encode_to_base64(password)

    var api = channelsFunction()

    api.loadVideos("https://api.cxntv.com/api/v1/videos/?type=canales&page_size=100&ordering=-id") { results in

        if results != nil {
            api.save_LiveChannels(results!)
            saver_user(userbase64, passbase64: passbase64, username: username, password: password)
            errormessage = ""
            completionHandler(true)

        }else{
            completionHandler(false)
        }
    }
}

And then login would call this asynchronously, too:

@IBAction func login(sender: AnyObject) {
    activity.hidden = false
    activity.startAnimating()
    if username.isEmpty || password.isEmpty {
        showAlert("Asegurese de ingresar un usuario y contraseña!")
    } else {
        userValid() { success in
            if !user.user_valid(self.username,password: self.password){
                self.showAlert("Usuario Invalido")
            }else{

            }

            dispatch_async(dispatch_get_main_queue(), {
                activity.hidden = true
                activity.stopAnimating()
            }
        }
    }
}

I'm sure I don't have all of your functions and variables right, but hopefully you can see the pattern: Don't use synchronous methods and use completionHandler parameters with your asynchronous methods.

--

If you want to see my original answer, that I posted before you shared the additional code, see the revision history of this answer. But the above outlines the basic approach.



回答2:

It happens because you show and hide it in the same method, not allowing the application to update the screen.

Try dispatching

// CODE***
activity.hidden = true
activity.stopAnimating()

asynchronously.



回答3:

Your shown code will never work as intended. Since you are in an IBAction it is safe to assume that when this function is called you generally are operating on the main thread.

I assume that the // CODE*** section takes a long time (otherwise there would be no need for an activity indicator).

Therefore you either currently do

  • actually perform the time-expensive operation on the main-thread and are therefore locking it -> very bad. Since you are on the main / UI-loop no UI updates happen, therefore no acitivity indicator is shown until the login is completely which is the exact time where you hide the indicator again.
  • or perform the operating asynchronously in the background (recommended way). If you do this however you have to move the stopAnimating to that background-task as well.

What you should do is the following:

@IBAction func login(sender: AnyObject) {
    activity.hidden = false
    activity.startAnimating()

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        // CODE***

        dispatch_async(dispatch_get_main_queue(), {
            activity.hidden = true
            activity.stopAnimating()
        })
    })
}

Important to note is that the stopping has to be done on the main thread again.



回答4:

Your code contains connection to the server so you the code between startAnimating and stopAnimating will be executed on a different thread, and that's why hide function will be executed before you notice.

What you have to do is to show the activityIndicator and hide it inside the connection code after you receive the response from the server. Also, you should write the code to hide the activityIndicator inside a dispatch like this:

dispatch_async(dispatch_get_main_queue(), { () -> Void in
    activity.hidden = true
    activity.stopAnimating()      
})

the answer can be more helpful if you post the rest of the code.

Edit:

You should remove the hide code from the login function and hide the activityIndicator in load_videos like this:

func load_videos(url :String)->Array<JSON>{
    ...

    request.getData({data, error -> Void in
        println("los datos")

        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            activity.hidden = true
            activity.stopAnimating()      
        })
        ...  
    })

    return resultados
}


回答5:

you should learn to use dispatch_async

dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)){
    //Code take time here
    //<#Code#>
    dispatch_async(dispatch_get_main_queue(){
        //update UI
        //<#Code#>
    }
 }

and remember that UPDATE UI in the main queue



标签: ios xcode swift