可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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