0

I have a custom view controller class which presents an alert controller when it receives a not connected notification from my network services class. I want to dismiss the alert controller when I receive a further connected notification from the network services class but I am unable to dismiss the presenting alert.

Here is my custom view controller class;

class CompanyPriceListVC: UITableViewController {
    
    // MARK: - Properties
    
    let companyStockSymbols = ["AAPL","AMZN", "MSFT", "GOOGL", "TSLA", "META","COINBASE:BTC", "BINANCE:BTCUSDT", "BINANCE:BNBBTC", "IC MARKETS:1", "IC MARKETS:2"]
    
    let defaultStockInfo = StockInfo(tradeConditions: nil, price: 0.00, symbol: "-", timestamp: 0.0, volume: 0.0)
    
    lazy var companyStockInfo = [StockInfo](repeating: defaultStockInfo, count: companyStockSymbols.count)
    
    var companyDetailsVC:CompanyDetailsVC?
    
    var tableRowSelected: Int?
    
    let defaultPriceHistory = PriceHistory(previous: nil, relative: 0.0, absolute: 0.0)
    
    lazy var priceHistory = [PriceHistory](repeating: defaultPriceHistory , count: companyStockSymbols.count)
    
    var deviceOrientation: Orientation = .portrait
    
    let activityIndicator: UIActivityIndicatorView = {
        let activityIndicator = UIActivityIndicatorView()
        activityIndicator.style = .large
        activityIndicator.hidesWhenStopped = true
        activityIndicator.color = .white
        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
        activityIndicator.startAnimating()
        return activityIndicator
    }()
    
    let isConnected = Notification.Name(rawValue: isConnectedNotificationKey)
    let isNotConnected = Notification.Name(rawValue: isDisconnectedNotificationKey)
    
    let alertController = UIAlertController(title: "Connectivity Error", message: "Network connection lost. Please check your WiFi settings, mobile data settings or reception coverage.", preferredStyle: .alert)
    
    // MARK: - Lifecycle Methods
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if UIDevice.current.orientation.isPortrait {
            self.deviceOrientation = .portrait
            self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
        } else {
            self.deviceOrientation = .landscape
            self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
        }
        
        configureNavigationBar()
        
        view.backgroundColor = .black
        
        configureTableView()
        
        configureConstraints()
        
        createObservers()
        
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    private func createObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.dismissAndFetchStockInfo), name: isConnected, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.notifyUserOnScreen(notification:)), name: isNotConnected, object: nil)
    }
    
    @objc private func fetchStockInfo() {
            NetworkServices.sharedInstance.fetchStockInfo(symbols: companyStockSymbols, delegate: self)
    }

    @objc private func notifyUserOnScreen(notification: NSNotification) {
        self.present(alertController, animated: true)
    }
    
    @objc public func dismissAndFetchStockInfo() {
        print("DEBUG: isBeingPresented: \(alertController.isBeingPresented)")
        if alertController.isBeingPresented {
            self.alertController.dismiss(animated: true, completion: nil)
        }
        fetchStockInfo()
    }
    
    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)
        
        coordinator.animate(alongsideTransition: { (context) in
            guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }
            
            if windowInterfaceOrientation.isPortrait {
                self.deviceOrientation = .portrait
                self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
                self.tableView.reloadData()
            } else {
                self.deviceOrientation = .landscape
                self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
                self.tableView.reloadData()
            }
        })
    }
    
    private var windowInterfaceOrientation: UIInterfaceOrientation? {
        return self.view.window?.windowScene?.interfaceOrientation
    }
    
    // MARK: - TableView Data Source Methods
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return companyStockInfo.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        switch deviceOrientation {
        case .portrait:
            let tableViewCellPortrait = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellPortrait", for: indexPath) as! CompanyStockCellPortrait
            
            tableViewCellPortrait.symbolValue.text = companyStockInfo[indexPath.row].symbol
            
            var latestPrice = companyStockInfo[indexPath.row].price
            latestPrice = round(latestPrice * 100.0) / 100.00
            tableViewCellPortrait.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
            
            if let _ = priceHistory[indexPath.row].previous {
                
                let absoluteDifference = priceHistory[indexPath.row].absolute
                let relativeDifference = priceHistory[indexPath.row].relative
                
                if absoluteDifference > 0.0 {
                    tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
                    tableViewCellPortrait.priceDirectionImageView.tintColor = .systemGreen
                    tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
                    tableViewCellPortrait.relativePriceDiffValue.textColor = .systemGreen
                    tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
                    tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemGreen
                } else if absoluteDifference < 0.0 {
                    tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
                    tableViewCellPortrait.priceDirectionImageView.tintColor = .systemRed
                    tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
                    tableViewCellPortrait.relativePriceDiffValue.textColor = .systemRed
                    tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
                    tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemRed
                } else {
                    tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
                    tableViewCellPortrait.priceDirectionImageView.tintColor = .white
                    tableViewCellPortrait.relativePriceDiffValue.text = "0.00%"
                    tableViewCellPortrait.relativePriceDiffValue.textColor = .white
                    tableViewCellPortrait.absolutePriceDiffValue.text = "(0.00)"
                    tableViewCellPortrait.absolutePriceDiffValue.textColor = .white
                }
            } else {
                tableViewCellPortrait.priceDirectionImageView.image = UIImage()
                tableViewCellPortrait.priceDirectionImageView.tintColor = .white
                tableViewCellPortrait.relativePriceDiffValue.text = ""
                tableViewCellPortrait.absolutePriceDiffValue.text = ""
            }
            
            return tableViewCellPortrait
            
            
        case .landscape:
            
            let tableViewCellLandscape = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellLandscape", for: indexPath) as! CompanyStockCellLandscape
            
            tableViewCellLandscape.symbolValue.text = companyStockInfo[indexPath.row].symbol
            
            var latestPrice = companyStockInfo[indexPath.row].price
            latestPrice = round(latestPrice * 100.0) / 100.00
            tableViewCellLandscape.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
            
            if let _ = priceHistory[indexPath.row].previous {
                
                let absoluteDifference = priceHistory[indexPath.row].absolute
                let relativeDifference = priceHistory[indexPath.row].relative
                
                if absoluteDifference > 0.0 {
                    tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
                    tableViewCellLandscape.priceDirectionImageView.tintColor = .systemGreen
                    tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
                    tableViewCellLandscape.relativePriceDiffValue.textColor = .systemGreen
                    tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
                    tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemGreen
                } else if absoluteDifference < 0.0 {
                    tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
                    tableViewCellLandscape.priceDirectionImageView.tintColor = .systemRed
                    tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
                    tableViewCellLandscape.relativePriceDiffValue.textColor = .systemRed
                    tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
                    tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemRed
                } else {
                    tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
                    tableViewCellLandscape.priceDirectionImageView.tintColor = .white
                    tableViewCellLandscape.relativePriceDiffValue.text = "0.00%"
                    tableViewCellLandscape.relativePriceDiffValue.textColor = .white
                    tableViewCellLandscape.absolutePriceDiffValue.text = "(0.00)"
                    tableViewCellLandscape.absolutePriceDiffValue.textColor = .white
                }
            } else {
                tableViewCellLandscape.priceDirectionImageView.image = UIImage()
                tableViewCellLandscape.priceDirectionImageView.tintColor = .white
                tableViewCellLandscape.relativePriceDiffValue.text = ""
                tableViewCellLandscape.absolutePriceDiffValue.text = ""
            }
            
            return tableViewCellLandscape
            
        }
    }
    
    // MARK: - TableView Delegate Methods
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100.0
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        companyDetailsVC = CompanyDetailsVC(companyStockInfo: companyStockInfo[indexPath.row])
        tableRowSelected = indexPath.row
        navigationController?.pushViewController(companyDetailsVC!, animated: true)
    }
    
    // MARK: - Helper Functions
    
    private func configureNavigationBar() {
        UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
        navigationController?.navigationBar.topItem?.title = "Trade Tracker"
        navigationController?.navigationBar.barStyle = .black
        navigationController?.navigationBar.prefersLargeTitles = true
    }
    
    private func configureTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .singleLine
        tableView.separatorColor = .white
        tableView.tableFooterView = UIView()
    }
    
    private func configureConstraints() {
        tableView.addSubview(activityIndicator)
        
        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
            activityIndicator.topAnchor.constraint(equalTo: tableView.topAnchor, constant: 250)
        ])
        
        tableView.layoutSubviews()
    }
}

// MARK: - Network Service Delegate Method

extension CompanyPriceListVC: NetworkServicesDelegate {
    
    func sendStockInfo(stocksInfo: [String : StockInfo]) {
        
        DispatchQueue.main.async {
            
            // Compute absolute and relative price differences
            for (index, symbol) in self.companyStockSymbols.enumerated() {
                if stocksInfo[symbol] != nil {
                    self.priceHistory[index].previous = self.companyStockInfo[index].price
                    self.companyStockInfo[index] = stocksInfo[symbol]!
                    
                    var latestPrice = self.companyStockInfo[index].price
                    latestPrice = round(latestPrice * 100.0) / 100.00
                    
                    if let previous = self.priceHistory[index].previous {
                        let previousPrice = round(previous * 100.0) / 100.0
                        var absoluteDifference = latestPrice - previousPrice
                        absoluteDifference = round(absoluteDifference * 100.0) / 100.0
                        self.priceHistory[index].absolute = absoluteDifference
                        
                        var relativeDifference = 0.0
                        if previousPrice != 0.00 {
                            relativeDifference = (absoluteDifference / previousPrice) * 100.0
                            relativeDifference = round(relativeDifference * 100) / 100.0
                        }
                        self.priceHistory[index].relative = relativeDifference
                    }
                }
            }
            
            self.tableView.reloadData()
            self.activityIndicator.stopAnimating()
                        
            guard let rowSelected = self.tableRowSelected else { return }
            self.companyDetailsVC!.companyStockInfo = self.companyStockInfo[rowSelected]
            self.companyDetailsVC!.relativeDifference = self.priceHistory[rowSelected].relative
            self.companyDetailsVC!.absoluteDifference = self.priceHistory[rowSelected].absolute
            
        }
    }
}

and here is my network services class;

import UIKit
import Starscream
import Network

protocol NetworkServicesDelegate: AnyObject {
    func sendStockInfo(stocksInfo: [String: StockInfo])
}

final class NetworkServices {
    
    static let sharedInstance = NetworkServices()
        
    var request = URLRequest(url: FINNHUB_SOCKET_STOCK_INFO_URL!)
    var socket: WebSocket!
    public private(set) var isConnected = false
 
    var stocksInfo: [String: StockInfo] = [:]
    
    var socketResults: [String: [StockInfo]] = [:]
    
    weak var delegate: NetworkServicesDelegate?
        
    var stockSymbols: [String] = []
    
    private init() {
        request.timeoutInterval = 5
        socket = WebSocket(request: request)
        socket.delegate = self
    }
    
    private let queue = DispatchQueue.global()
    
    private let monitor = NWPathMonitor()
    
    public func startMonitoring() {
        monitor.start(queue: queue)
        
        self.monitor.pathUpdateHandler = { [weak self] path in
            if path.status == .satisfied {
                // connect the socket
                self?.socket.connect()
            } else {
                // disconnect the socket
                self?.socket.disconnect()
                // post notification that socket is now disconnected
                DispatchQueue.main.async {
                    let name = Notification.Name(rawValue: isDisconnectedNotificationKey)
                    NotificationCenter.default.post(name: name, object: nil)
                }
            }
        }
        
    }
    
    public func stopMonitoring() {
        monitor.cancel()
    }
    
    func fetchStockInfo(symbols: [String], delegate: CompanyPriceListVC) {
                
        stockSymbols = symbols
        
        self.delegate = delegate
                
        for symbol in symbols {
            
            let string = FINNHUB_SOCKET_MESSAGE_STRING + symbol + "\"}"
            
            socket.write(string: string)

        }
    }

    private func parseJSONSocketData(_ socketString: String) {
        
        self.socketResults = [:]
        self.stocksInfo = [:]
        
        let decoder = JSONDecoder()
        do {
            let socketData = try decoder.decode(SocketData.self, from: socketString.data(using: .utf8)!)
            guard let stockInfoData = socketData.data else { return }
            for stockInfo in stockInfoData {
                let symbol = stockInfo.symbol
                if self.socketResults[symbol] == nil {
                    self.socketResults[symbol] = [StockInfo]()
                }
                self.socketResults[symbol]?.append(stockInfo)
            }
            
            for (symbol, stocks) in self.socketResults {
                for item in stocks {
                    if self.stocksInfo[symbol] == nil {
                        self.stocksInfo[symbol] = item
                    } else if item.timestamp > self.stocksInfo[symbol]!.timestamp {
                        self.stocksInfo[symbol] = item
                    }
                }
            }
            self.delegate?.sendStockInfo(stocksInfo: self.stocksInfo)
            
        } catch {
            print("DEBUG: error: \(error.localizedDescription)")
        }
    }
    
    func fetchCompanyInfo(symbol: String, completion: @escaping (CompanyInfo?, UIImage?)->()) {
        
        let urlString = FINNHUB_HTTP_COMPANY_INFO_URL_STRING + symbol + "&token=" + FINNHUB_API_TOKEN
        
        guard let url = URL(string: urlString) else { return }
        
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                print("Error fetching company info: \(error)")
            }
            guard let data = data else { return }
            let decoder = JSONDecoder()
            
            do {
                let companyInfo = try decoder.decode(CompanyInfo.self, from: data)
                
                guard let logoURL = URL(string: companyInfo.logo) else { return }
                
                let task = URLSession.shared.dataTask(with: logoURL) { data, response, error in
                    if let error = error {
                        print("Error fetching logo image: \(error)")
                    }
                    guard let data = data else { return }
                    
                    guard let logoImage = UIImage(data: data) else { return }
                    
                    completion(companyInfo, logoImage)
                    
                }
                task.resume()
            } catch {
                print("Error decoding JSON: \(error)")
                completion(nil, nil)
            }
        }
        task.resume()
    }
}

extension NetworkServices: WebSocketDelegate {
    
    func didReceive(event: WebSocketEvent, client: WebSocket) {
        
        switch event {
        case .connected(_):
            self.isConnected = true
            // post notification that socket is now connected
            let name = Notification.Name(rawValue: isConnectedNotificationKey)
            NotificationCenter.default.post(name: name, object: nil)

        case .disconnected(let reason, let code):
            print("DEBUG: Got disconnected.")
            self.isConnected = false

        case .cancelled:
            print("DEBUG: cancelled.")
            // post notification that socket is not connected

        case .reconnectSuggested(let suggestReconnect):
            print("DEBUG: suggestReconnect = \(suggestReconnect)")
            // post notification that socket is not connected
            
        case .viabilityChanged(let viabilityChanged):
            print("DEBUG: viabilityChange = \(viabilityChanged)")
            
        case .error(let error):
            print("DEBUG error: \(String(describing: error?.localizedDescription))")
            
        case .text(let socketString):
            parseJSONSocketData(socketString)
            
        default:
            break
        }
    }
}

I've tried querying the isBeingPresented property of the alert controller but it always tests as false even though I can see the alert controller is being presented.

Stephen501
  • 153
  • 8

1 Answers1

0

You could do a check if the currently presented UIViewController is actually the alertController like this:

@objc public func dismissAndFetchStockInfo() {
    if presentedViewController == alertController {
        alertController.dismiss(animated: true, completion: nil)
    }

    fetchStockInfo()
}

This is because isBeingPresented is only valid inside the view[Will|Did][Disa|A]ppear methods.

yanakievv
  • 42
  • 5
  • @VladimirYanahiev Yes that works, thanks. But why doesn't my code work it seems fine? – Stephen501 Mar 28 '22 at 16:39
  • @Stephen501 If the answer given doesn't answer your actual question, you probably shouldn't accept it. Instead, edit your question to clarify what you really want to know the answer to. (Of course if the answer _does_ answer your question, great, but then you shouldn't be asking further questions in a comment.) – matt Mar 28 '22 at 16:47
  • I've updated the comment, the current documentation does not mention that but it definitely used to. [Here](https://stackoverflow.com/a/23620428/14823612) is a similar discussion. – yanakievv Mar 28 '22 at 16:59