Activity 当按下 navigationItem 按钮时指示器没有动画
Activity Indicator is not animated when press navigationItem button
我尝试在按下 navigationItem 的按钮时触发 activity 指示器的动画。但是我发现 activity 指标没有旋转。我尝试将 scanerIndicator.startAnimating()
放到主线程,但是没有帮助。
代码收集了路由器打开的端口,我想在按下 navigationItem 按钮时开始旋转,并在 returned openPorts 时停止旋转。感谢 clue/hint 哪里有问题?
override func viewDidLoad() {
super.viewDidLoad()
...
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(startScan))
...
}
@objc func startScan() {
scanerIndicator.startAnimating()
if let address = serverAddress.text, !address.isEmpty {
if let start = Int(startPort.text!) {
if let stop = Int(stopPort.text!) {
if start < stop {
openPorts = netUtility.scanPorts(address: address, start: start, stop: stop)
print("Open Open: \(openPorts)")
if !openPorts.isEmpty {
scanerIndicator.stopAnimating()
table.reloadData()
} else {
showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
}
} else {
showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
}
收集端口的代码:
// MARK: - Port Scaner
// Get number of threads for scan ports
func getSegmentsQueues(min: Int, max: Int, maxPerSegment: Int) -> [[Int]] {
var start: Int = min
var portSegments = [[Int]]()
while start <= max {
var _portSegment = [Int]()
for _ in 1...maxPerSegment {
if start <= max {
_portSegment.append(start)
}
start += 1
}
portSegments.append(_portSegment)
}
return portSegments
}
// Crate queques for scan ports by segments
func QueueDispatchPort(address: String, minPort: Int, maxPort: Int, segmentsQueues: (Int, Int, Int) -> [[Int]]) -> [Int] {
var openPorts : [Int] = []
let segmentPorts = segmentsQueues(minPort, maxPort, 1);
let group = DispatchGroup()
for segment in segmentPorts {
group.enter()
DispatchQueue.global().async {
for port in segment {
let client = TCPClient(address: address, port: Int32(port))
switch client.connect(timeout: 2) {
case .success:
openPorts.append(port)
case .failure(_):
print("port \(port) closed")
}
client.close()
}
group.leave()
}
}
group.wait()
return openPorts
}
// Scans ports from an address and a range given by the user
func scanPorts(address : String, start : Int, stop : Int) -> [Int] {
let openPorts = QueueDispatchPort(
address: address, minPort: start, maxPort: stop, segmentsQueues:
getSegmentsQueues(min:max:maxPerSegment:))
return openPorts
}
代码更新,我将代码块(扫描端口)放在主线程上,这次删除了stopAnimating()
。 activity 指标在长 运行 代码 return(DispatchQueue.main 中的内容)之后进行动画处理。还是不行...
@objc func startScan() {
scanerIndicator.startAnimating()
DispatchQueue.main.async { [self] in
if let address = serverAddress.text, !address.isEmpty {
if let start = Int(startPort.text!) {
if let stop = Int(stopPort.text!) {
if start < stop {
openPorts = netUtility.scanPorts(address: address, start: start, stop: stop)
print("Open Open: \(openPorts)")
if !openPorts.isEmpty {
table.reloadData()
} else {
showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
}
} else {
showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
}
}
有点难以理解您的代码在做什么,但我的猜测是即使您使用队列进行端口扫描,因为您使用的是 DispatchGroup,所以代码会阻塞直到所有端口扫描完成了。
如果您有执行以下操作的同步代码:
- 开始动画 activity 指标
- 执行 long-running 任务(在主线程上)
- 停止动画 activity 指标
那你就再也看不到动画了。问题是在您的代码 return 和您的应用程序访问其事件循环之前动画不会开始。
您需要像这样编写代码:
scanerIndicator.startAnimating()
DispatchQueue.main.async {
//Do long-running task
scanerIndicator.stopAnimating()
}
之所以可行,是因为在调用 startAnimating()
之后,您将异步调用添加到主调度队列(在主线程上),然后是 return。您的应用程序的函数调用堆栈全部 return,您的应用程序访问事件循环,并且 activity 指示器开始旋转。然后系统选择您添加到主队列的异步任务并开始 运行 该任务。最后,当您的 long-running 任务完成时,您关闭 activity 指示器(在内部调用的代码中
async()
电话。
终于,终于让这段代码生效了,DispatchQueue
真是烧了一整天的脑子!
我的修复流程:
func abc() {
activityIndicator.startAnimating()
DispatchQueue.global(qos: .default).async {
// put your heavy code here
DispatchQueue.main.async {
// UI code must be on main thread
activityIndicator.stopAnimating()
}
}
}
我做什么?
- 首先,我将两个端口扫描代码合并为一个函数。
- 将long-time消费代码放入
DispatchQueue.global(qos: .default).async
。我首先尝试使用主线程,但它失败了,而且 long-running 代码应该在后台线程中。 global()
是后台线程吗?稍后我会挖掘更多:)
- 当这段繁重的代码完成时,使用@escaping completionHandler 作为回调将 openPorts 提供给调用者。因为在这种情况下你不能使用 return 值,因为我们不知道繁重的代码何时完成,所以我们使用回调将参数传递回调用者。
// Scans ports from an address and a range given by the user
func scanPorts(address : String, start : Int, stop : Int, completion: @escaping ([Int]) -> ()) {
DispatchQueue.global(qos: .default).async {
for port in start...stop {
let client = TCPClient(address: address, port: Int32(port))
switch client.connect(timeout: 2) {
case .success:
self.openPorts.append(port)
print("HH: port: \(self.openPorts)")
case .failure(_):
print("port \(port) closed")
}
client.close()
}
completion(self.openPorts)
}
}
调用者:请记住将 UI 代码放在主线程上。
@objc func startScan() {
scanerIndicator.startAnimating()
view.endEditing(true)
self.view.isUserInteractionEnabled = false
if let address = serverAddress.text, !address.isEmpty {
if let start = Int(startPort.text!) {
if let stop = Int(stopPort.text!) {
if start < stop {
netUtility.scanPorts(address: address, start: start, stop: stop) { [self] (availablePorts) in
openPorts = availablePorts
print("$Open ports: \(self.openPorts)")
if !openPorts.isEmpty {
DispatchQueue.main.async {
table.reloadData()
self.scanerIndicator.stopAnimating()
}
} else {
showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
}
}
} else {
showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
}
我尝试在按下 navigationItem 的按钮时触发 activity 指示器的动画。但是我发现 activity 指标没有旋转。我尝试将 scanerIndicator.startAnimating()
放到主线程,但是没有帮助。
代码收集了路由器打开的端口,我想在按下 navigationItem 按钮时开始旋转,并在 returned openPorts 时停止旋转。感谢 clue/hint 哪里有问题?
override func viewDidLoad() {
super.viewDidLoad()
...
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(startScan))
...
}
@objc func startScan() {
scanerIndicator.startAnimating()
if let address = serverAddress.text, !address.isEmpty {
if let start = Int(startPort.text!) {
if let stop = Int(stopPort.text!) {
if start < stop {
openPorts = netUtility.scanPorts(address: address, start: start, stop: stop)
print("Open Open: \(openPorts)")
if !openPorts.isEmpty {
scanerIndicator.stopAnimating()
table.reloadData()
} else {
showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
}
} else {
showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
}
收集端口的代码:
// MARK: - Port Scaner
// Get number of threads for scan ports
func getSegmentsQueues(min: Int, max: Int, maxPerSegment: Int) -> [[Int]] {
var start: Int = min
var portSegments = [[Int]]()
while start <= max {
var _portSegment = [Int]()
for _ in 1...maxPerSegment {
if start <= max {
_portSegment.append(start)
}
start += 1
}
portSegments.append(_portSegment)
}
return portSegments
}
// Crate queques for scan ports by segments
func QueueDispatchPort(address: String, minPort: Int, maxPort: Int, segmentsQueues: (Int, Int, Int) -> [[Int]]) -> [Int] {
var openPorts : [Int] = []
let segmentPorts = segmentsQueues(minPort, maxPort, 1);
let group = DispatchGroup()
for segment in segmentPorts {
group.enter()
DispatchQueue.global().async {
for port in segment {
let client = TCPClient(address: address, port: Int32(port))
switch client.connect(timeout: 2) {
case .success:
openPorts.append(port)
case .failure(_):
print("port \(port) closed")
}
client.close()
}
group.leave()
}
}
group.wait()
return openPorts
}
// Scans ports from an address and a range given by the user
func scanPorts(address : String, start : Int, stop : Int) -> [Int] {
let openPorts = QueueDispatchPort(
address: address, minPort: start, maxPort: stop, segmentsQueues:
getSegmentsQueues(min:max:maxPerSegment:))
return openPorts
}
代码更新,我将代码块(扫描端口)放在主线程上,这次删除了stopAnimating()
。 activity 指标在长 运行 代码 return(DispatchQueue.main 中的内容)之后进行动画处理。还是不行...
@objc func startScan() {
scanerIndicator.startAnimating()
DispatchQueue.main.async { [self] in
if let address = serverAddress.text, !address.isEmpty {
if let start = Int(startPort.text!) {
if let stop = Int(stopPort.text!) {
if start < stop {
openPorts = netUtility.scanPorts(address: address, start: start, stop: stop)
print("Open Open: \(openPorts)")
if !openPorts.isEmpty {
table.reloadData()
} else {
showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
}
} else {
showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
}
}
有点难以理解您的代码在做什么,但我的猜测是即使您使用队列进行端口扫描,因为您使用的是 DispatchGroup,所以代码会阻塞直到所有端口扫描完成了。
如果您有执行以下操作的同步代码:
- 开始动画 activity 指标
- 执行 long-running 任务(在主线程上)
- 停止动画 activity 指标
那你就再也看不到动画了。问题是在您的代码 return 和您的应用程序访问其事件循环之前动画不会开始。
您需要像这样编写代码:
scanerIndicator.startAnimating()
DispatchQueue.main.async {
//Do long-running task
scanerIndicator.stopAnimating()
}
之所以可行,是因为在调用 startAnimating()
之后,您将异步调用添加到主调度队列(在主线程上),然后是 return。您的应用程序的函数调用堆栈全部 return,您的应用程序访问事件循环,并且 activity 指示器开始旋转。然后系统选择您添加到主队列的异步任务并开始 运行 该任务。最后,当您的 long-running 任务完成时,您关闭 activity 指示器(在内部调用的代码中
async()
电话。
终于,终于让这段代码生效了,DispatchQueue
真是烧了一整天的脑子!
我的修复流程:
func abc() {
activityIndicator.startAnimating()
DispatchQueue.global(qos: .default).async {
// put your heavy code here
DispatchQueue.main.async {
// UI code must be on main thread
activityIndicator.stopAnimating()
}
}
}
我做什么?
- 首先,我将两个端口扫描代码合并为一个函数。
- 将long-time消费代码放入
DispatchQueue.global(qos: .default).async
。我首先尝试使用主线程,但它失败了,而且 long-running 代码应该在后台线程中。global()
是后台线程吗?稍后我会挖掘更多:) - 当这段繁重的代码完成时,使用@escaping completionHandler 作为回调将 openPorts 提供给调用者。因为在这种情况下你不能使用 return 值,因为我们不知道繁重的代码何时完成,所以我们使用回调将参数传递回调用者。
// Scans ports from an address and a range given by the user
func scanPorts(address : String, start : Int, stop : Int, completion: @escaping ([Int]) -> ()) {
DispatchQueue.global(qos: .default).async {
for port in start...stop {
let client = TCPClient(address: address, port: Int32(port))
switch client.connect(timeout: 2) {
case .success:
self.openPorts.append(port)
print("HH: port: \(self.openPorts)")
case .failure(_):
print("port \(port) closed")
}
client.close()
}
completion(self.openPorts)
}
}
调用者:请记住将 UI 代码放在主线程上。
@objc func startScan() {
scanerIndicator.startAnimating()
view.endEditing(true)
self.view.isUserInteractionEnabled = false
if let address = serverAddress.text, !address.isEmpty {
if let start = Int(startPort.text!) {
if let stop = Int(stopPort.text!) {
if start < stop {
netUtility.scanPorts(address: address, start: start, stop: stop) { [self] (availablePorts) in
openPorts = availablePorts
print("$Open ports: \(self.openPorts)")
if !openPorts.isEmpty {
DispatchQueue.main.async {
table.reloadData()
self.scanerIndicator.stopAnimating()
}
} else {
showErrorMessage(errorTitle: "Not at all", errorMessage: "No open ports were found")
}
}
} else {
showErrorMessage(errorTitle: "Range error", errorMessage: "Start port should be smaller than stop port")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
} else {
showErrorMessage(errorTitle: "Empty fields", errorMessage: "Please fill all the necessary data")
}
}