I try to trigger activity indicator's animation when press navigationItem's button. But I found the activity indicator is not spinning. And I try to put scanerIndicator.startAnimating() to main thread, however no help.
The code is collected the opened port of router, I want to start the spinning when press navigationItem button and stop the spinning when openPorts was returned. Appreciate for any clue/hint about where is wrong?
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")
}
}
Code to collect the ports:
// 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
}
Code update, I put the chunk of code(scan port) on main thread, and remove stopAnimating() for this time. The activityIndicator is animated after long-run code return(what in DispatchQueue.main). Still not work...
@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")
}
}
}
Its a little hard to understand what your code is doing, but my guess is that even though you're using queues to do your port scanning, because you are using a DispatchGroup, the code blocks until all the port scanning is complete.
If you have synchronous code that does the following:
Then you never see the animation. The problem is that the animation doesn't start until your code returns and your app visits its event loop.
You instead need to write your code like this:
That works because after calling
startAnimating(), you add an async call to the main dispatch queue (on the main thread), then return. Your app's function call stack all returns, your app visits the event loop, and the activity indicator begins spinning. The system then picks up the async task you added to the main queue and begins running that task. Finally, when your long-running task is complete, you turn off the activity indicator (In the code that's called inside theasync()call.