Why is my alert controller not dismissing in my Swift app? - swift

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.

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.

Related

use a switch to change the value of boolean saved core data value

In my swift code below right now the code saves 2 boolean values to a core data boolean value. The two values are true false. I would like the user to turn on the switch and have the 2nd value be true as well. So it would be true true. I am trying to do that in func alternate() but I dont know how to exactly effect a specific value. Looking for any kind of help.
import UIKit;import CoreData
class ViewController: UIViewController {
var lbl = UILabel()
var sw = UISwitch()
var checkmarkButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
[ lbl,sw,checkmarkButton].forEach {
view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
$0.layer.borderWidth = 1
}
helpBool.shareInstance.saveBoolean(true)
helpBool.shareInstance.saveBoolean(false)
getBool(imageNo: 1)
}
func getBool(imageNo:Int) {
// first check the array bounds
let info = helpBool.shareInstance.fetchBool()
if info.count > imageNo {
// if info[imageNo].bool {
if info[imageNo].bool == true {
checkmarkButton.setImage(UIImage(named:"unnamed"), for: .normal);
}
if info[imageNo].bool == false {
checkmarkButton.setImage(nil, for: .normal);
}
// }
}
}
#objc func alternate(){
//fetch alternate
getBool(imageNo: 1)
if sw.isOn = true {
helpBool.shareInstance.saveBoolean()
}
else {
helpBool.shareInstance.saveBoolean()
}
}
}
class helpBool: UIViewController{
static let shareInstance = helpBool()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveBoolean(_ boolean: Bool) {
let imageInstance = OnOff(context: context)
imageInstance.bool = boolean
do {
try context.save()
print("text is saved")
} catch {
print(error.localizedDescription)
}
}
func fetchBool() -> [OnOff] {
var fetchingImage = [OnOff]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "OnOff")
do {
fetchingImage = try context.fetch(fetchRequest) as! [OnOff]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}

Getting error when using Google place api in swift

I am using google place api to get location in my swift application. In my app when i am going to place api screen by clicking on the textfield present inside the cell of my tableview it is working fine. But when i am using the same code to show the place api screen by clicking on the textfield present inside the uiview, after selecting any place it is comming back to the previous screen, but once again the place api screen opening automatically. i have added my code, can anyonce see it and let me know the solution?
import GooglePlaces
class AddPatientViewController: UIViewController{
//MARK:- Properties
#IBOutlet weak var location_TextField: UITextField!
//MARK:- Variables
var locationManager = CLLocationManager()
var mAppDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate;
//MARK:- LifeCycles Methods
override func viewDidLoad() {
super.viewDidLoad()
setDelegate()
}
//MARK:- Helpers
private func setDelegate(){
location_TextField.delegate = self
}
//MARK: - getLocation
func getCurrentLatLongFromCLlocation() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
switch CLLocationManager.authorizationStatus() {
case .notDetermined, .restricted, .denied:
print("No access")
getPermissionAgain()
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
if let coordinateValue = locationManager.location?.coordinate {
mAppDelegate.cordinateVal = coordinateValue
self.getPlaceDataUsingLocation()
}
#unknown default:
getPermissionAgain()
}
} else {
print("Location services are not enabled")
getPermissionAgain()
}
}
func getPermissionAgain(){
let alert = UIAlertController(title: "Simplr", message: "Required location permission to set your search location", preferredStyle: .alert)
let okayAction = UIAlertAction(title: "Dismiss", style: .default, handler: nil)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (action) in
if let appSettings = URL(string: UIApplication.openSettingsURLString) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(appSettings, options: [:], completionHandler: { (enabled) in
DispatchQueue.main.async {
//UIApplication.shared.registerForRemoteNotifications()
}
})
} else {
// Fallback on earlier versions
}
}
}
alert.addAction(okayAction)
alert.addAction(settingsAction)
present(alert, animated: false, completion: nil)
}
func getPlaceDataUsingLocation(){
DispatchQueue.global().async {
// Specify the place data types to return.
let placesClient = GMSPlacesClient.init()
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) | UInt(GMSPlaceField.coordinate.rawValue) | UInt(GMSPlaceField.formattedAddress.rawValue))
placesClient.currentPlace { (list, error) in
if let error = error {
print("An error occurred: \(error.localizedDescription)")
return
}
if let placeLikelihoodList = list?.likelihoods {
for likelihood in placeLikelihoodList {
let place = likelihood.place
print("\n\n")
print("Current Place name \(String(describing: place.name)) at likelihood \(likelihood.likelihood)")
print("Current PlaceID \(String(describing: place.placeID))")
print("Formatted Address: \(String(describing: place.formattedAddress))")
// print("Fields: \(place.)")
print("\nAddress Component: \(String(describing: place.addressComponents))\n")
print("Type: \(String(describing: place.types))")
}
}
}
}
}
}
//MARK: - UITextfield Delegate Methods
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool{
if textField == location_TextField{
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
// autocompleteController.primaryTextColor = UIColor(hex: "#4C2B6D")
// autocompleteController.secondaryTextColor = UIColor(hex: "#4C2B6D")
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) | UInt(GMSPlaceField.coordinate.rawValue) |
UInt(GMSPlaceField.formattedAddress.rawValue) | UInt(GMSPlaceField.addressComponents.rawValue))
autocompleteController.placeFields = fields
autocompleteController.navigationController?.navigationBar.barTintColor = .blue
autocompleteController.navigationController?.navigationBar.prefersLargeTitles = true
// Specify a filter.
let filter = GMSAutocompleteFilter()
filter.type = .geocode
autocompleteController.autocompleteFilter = filter
// Display the autocomplete view controller.
autocompleteController.modalPresentationStyle = .fullScreen
present(autocompleteController, animated: true, completion: nil)
}
return true
}
}
//MARK: - GMSAutocompleteViewControllerDelegate
#available(iOS 13.0, *)
extension AddPatientViewController: GMSAutocompleteViewControllerDelegate {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
print("selected place is \(getFormattedAddressFromAddressComponents(place: place))")
location_TextField.text = getFormattedAddressFromAddressComponents(place: place)
self.dismiss(animated: true)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
self.dismiss(animated: true)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
func getFormattedAddressFromAddressComponents(place: GMSPlace) -> String{
var neighborhood = ""
var locality = ""
var sublocality = ""
var administrative_area_level_1 = ""
var formatedAddress = ""
if let addressComponents : Array<GMSAddressComponent> = place.addressComponents{
print(addressComponents)
for component in addressComponents{
if component.types.contains("administrative_area_level_1"){
if let shortName = component.shortName{
administrative_area_level_1 = shortName
}
}
else if component.types.contains("neighborhood")
{
neighborhood = component.name
} else if component.types.contains("sublocality")
{
sublocality = component.name
}else if component.types.contains("locality")
{
locality = component.name
}
}
var shouldAddComma = false
if !isNullObject(anyObject: administrative_area_level_1){
shouldAddComma = true
}
if !isNullObject(anyObject: neighborhood){
formatedAddress = neighborhood
}else if !isNullObject(anyObject: sublocality){
formatedAddress = sublocality
}
else if !isNullObject(anyObject: locality){
formatedAddress = locality
}
if !isNullObject(anyObject: formatedAddress){
if shouldAddComma{
formatedAddress = "\(formatedAddress),\(administrative_area_level_1)"
}
}
}
return formatedAddress
}
}

Custom Collection View layout: 'Invalid parameter not satisfying: self.supplementaryViewProvider'

I am using two collectionViews in my view controller. 1 has a custom layout and custom attributes for its supplementary views.
Here is the view controller:
class ProgressViewController: UIViewController {
private lazy var data = fetchData()
private lazy var recordsDataSource = makeRecordsDataSource()
private lazy var timelineDataSource = makeTimelineDataSource()
fileprivate typealias RecordsDataSource = UICollectionViewDiffableDataSource<YearMonthDay, TestRecord>
fileprivate typealias RecordsDataSourceSnapshot = NSDiffableDataSourceSnapshot<YearMonthDay, TestRecord>
fileprivate typealias TimelineDataSource = UICollectionViewDiffableDataSource<TimelineSection,YearMonthDay>
fileprivate typealias TimelineDataSourceSnapshot = NSDiffableDataSourceSnapshot<TimelineSection,YearMonthDay>
private var timelineMap = TimelineMap()
private var curItemIndex = 0
private var curRecordsOffset: CGFloat = 0
private var curTimelineOffset: CGFloat = 0
private var timelineStart: Date?
#IBOutlet var recordsCollectionView: UICollectionView!
#IBOutlet var timelineCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
data = fetchData()
timelineStart = Array(data.keys).first?.date
configureRecordsDataSource()
configureTimelineDataSource()
configureTimelineSupplementaryViews()
applyRecordsSnapshot()
applyTimelineSnapshot()
if let collectionViewLayout = timelineCollectionView.collectionViewLayout as? TimelineLayout {
collectionViewLayout.delegate = self
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var i: CGFloat = 0
for _ in data {
let recordOffset = self.recordsCollectionView.frame.width * i
let timelineOffset = (timelineCollectionView.frame.width + CGFloat(10)) * i
timelineMap.set(recordOffset: recordOffset, timelineOffset: timelineOffset)
i += 1
}
}
private func fetchData() -> [YearMonthDay:[TestRecord]] {
var data: [YearMonthDay:[
TestRecord]] = [:]
var testRecords:[TestRecord] = []
for i in 0...6{
let testRecord = TestRecord(daysBack: i*2, progression: 0)
testRecords.append(testRecord)
}
for record in testRecords {
let ymd = YearMonthDay(date:record.timeStamp,records: [])
if var day = data[ymd] {
day.append(record)
} else {
data[ymd] = [record]
}
}
return data
}
extension ProgressViewController {
//RECORDS data
fileprivate func makeRecordsDataSource() -> RecordsDataSource {
let dataSource = RecordsDataSource(
collectionView: recordsCollectionView,
cellProvider: { (collectionView, indexPath, testRecord) ->
UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RecordCollectionViewCell.identifier, for: indexPath) as? RecordCollectionViewCell
cell?.configure(with: testRecord)
return cell
})
return dataSource
}
func configureRecordsDataSource() {
self.recordsCollectionView.register(RecordCollectionViewCell.nib, forCellWithReuseIdentifier: RecordCollectionViewCell.identifier)
}
func applyRecordsSnapshot() {
// 2
var snapshot = RecordsDataSourceSnapshot()
for (ymd,records) in data {
snapshot.appendSections([ymd])
snapshot.appendItems(records,toSection: ymd)
}
recordsDataSource.apply(snapshot, animatingDifferences: false)
}
}
extension ProgressViewController {
enum TimelineSection {
case main
}
fileprivate func makeTimelineDataSource() -> TimelineDataSource {
let dataSource = TimelineDataSource(
collectionView: self.timelineCollectionView,
cellProvider: { (collectionView, indexPath, testRecord) ->
UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TimelineDayCell.identifier, for: indexPath) as? TimelineDayCell
cell?.backgroundColor = .orange
cell?.dayLabel.text = String(indexPath.section)+","+String(indexPath.row)
return cell
})
return dataSource
}
func configureTimelineDataSource() {
self.timelineCollectionView!.register(TimelineDayCell.nib, forCellWithReuseIdentifier: TimelineDayCell.identifier)
timelineCollectionView.register(
UINib(nibName: "MonthHeader", bundle: nil),
forSupplementaryViewOfKind: TimelineLayout.Element.month.kind,
withReuseIdentifier: TimelineLayout.Element.month.id)
timelineCollectionView.register(
UINib(nibName: "TimelineDayCell", bundle: nil),
forSupplementaryViewOfKind: TimelineLayout.Element.day.kind,
withReuseIdentifier: TimelineLayout.Element.day.id)
timelineCollectionView.register(
UINib(nibName: "YearLabel", bundle: nil),
forSupplementaryViewOfKind: TimelineLayout.Element.year.kind,
withReuseIdentifier: TimelineLayout.Element.year.id)
}
func applyTimelineSnapshot(animatingDifferences: Bool = false) {
// 2
var snapshot = TimelineDataSourceSnapshot()
snapshot.appendSections([.main])
snapshot.appendItems(Array(data.keys))
timelineDataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
func configureTimelineSupplementaryViews(){
timelineDataSource.supplementaryViewProvider = { (
collectionView: UICollectionView,
kind: String,
indexPath: IndexPath) -> UICollectionReusableView? in
switch kind {
case TimelineLayout.Element.month.kind:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TimelineLayout.Element.month.id, for: indexPath)
if let monthLabelView = supplementaryView as? MonthHeader {
let active = Calendar.current.date(byAdding: .month, value: indexPath.item, to: self.timelineStart!)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM"
let monthString = dateFormatter.string(from: active!)
monthLabelView.monthLabel.text = monthString
}
return supplementaryView
case TimelineLayout.Element.year.kind:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TimelineLayout.Element.year.id, for: indexPath)
if let yearLabelView = supplementaryView as? YearLabel {
let labelDate = Calendar.current.date(byAdding: .year, value: indexPath.item, to: self.timelineStart!)!
let year = Calendar.current.component(.year, from: labelDate)
yearLabelView.yearLabel.text = String(year)
}
return supplementaryView
default:
fatalError("This should never happen!!")
}
}
}
}
extension ProgressViewController: TimelineLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, dateAtIndexPath indexPath: IndexPath) -> Date? {
return timelineDataSource.itemIdentifier(for: indexPath)?.date
}
}
Then in my custom TimelineLayout I have
class TimelineLayout: UICollectionViewLayout {
weak var delegate: TimelineLayoutDelegate!
enum Element: String {
case day
case month
case year
var id: String {
return self.rawValue
}
var kind: String {
return "Kind\(self.rawValue.capitalized)"
}
}
private var oldBounds = CGRect.zero
private var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
private var contentWidth = CGFloat()
private var cache = [Element: [IndexPath: UICollectionViewLayoutAttributes]]()
private var monthHeight: CGFloat = 19
private var yearHeight: CGFloat = 11
private var dayHeight: CGFloat = 30
private var cellWidth: CGFloat = 2.5
private var collectionViewStartY: CGFloat {
guard let collectionView = collectionView else {
return 0
}
return collectionView.bounds.minY
}
private var collectionViewHeight: CGFloat {
return collectionView!.frame.height
}
override public var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: collectionViewHeight)
}
}
extension TimelineLayout {
override public func prepare() {
guard let collectionView = collectionView,
cache.isEmpty else {
return
}
collectionView.decelerationRate = .fast
updateInsets()
cache.removeAll(keepingCapacity: true)
cache[.year] = [IndexPath: UICollectionViewLayoutAttributes]()
cache[.month] = [IndexPath: UICollectionViewLayoutAttributes]()
cache[.day] = [IndexPath: UICollectionViewLayoutAttributes]()
oldBounds = collectionView.bounds
var timelineStart: Date?
var timelineEnd: Date?
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let cellIndexPath = IndexPath(item: item, section: 0)
guard let cellDate = delegate.collectionView(collectionView, dateAtIndexPath: cellIndexPath) else {
return
}
if item == 0 {
let firstDate = cellDate
timelineStart = Calendar.current.startOfDay(for: firstDate)
}
if item == collectionView.numberOfItems(inSection: 0) - 1 {
timelineEnd = cellDate
}
let startX = CGFloat(cellDate.days(from: timelineStart!)) * cellWidth
let dayCellattributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
dayCellattributes.frame = CGRect(x: startX, y: collectionViewStartY + yearHeight + monthHeight, width: cellWidth, height: dayHeight)
cache[.day]?[cellIndexPath] = dayCellattributes
contentWidth = max(startX + cellWidth,contentWidth)
}
///TODO - what if there are no items in the section....
guard let monthStart = timelineStart?.startOfMonth(), let monthEnd = timelineEnd?.endOfMonth() else { return }
let begin = Calendar.current.date(byAdding: .month, value: -4, to: monthStart)
let end = Calendar.current.date(byAdding: .month, value: 4, to: monthEnd)
var date: Date = begin!
let initalOffset = CGFloat((timelineStart?.days(from: date))!) * cellWidth
var monthOffset: CGFloat = 0
var monthIndex: Int = 0
var yearIndex: Int = 0
while date <= end! {
let daysInMonth = Calendar.current.range(of: .day, in: .month, for: date)?.count
let monthWidth = cellWidth * CGFloat(daysInMonth!)
//let monthIndex = date.months(from: timelineStart!)
let monthLabelIndexPath = IndexPath(item: monthIndex, section: 0)
let monthLabelAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: Element.month.kind, with: monthLabelIndexPath)
let startX = monthOffset - initalOffset
monthLabelAttributes.frame = CGRect(x: startX, y: collectionViewStartY + yearHeight, width: monthWidth, height: monthHeight)
print(startX,"spaghet")
cache[.month]?[monthLabelIndexPath] = monthLabelAttributes
monthOffset += monthWidth
if Calendar.current.component(.month, from: date) == 1 || yearIndex == 0 {
//draw year
//let year = Calendar.current.component(.year, from: date)
//let yearIndex = date.years(from: timelineStart!)
let daysFromStartOfYear = date.days(from: date.startOfYear())
let startYearX = startX - CGFloat(daysFromStartOfYear) * cellWidth
let yearLabelIndexPath = IndexPath(item: yearIndex, section: 0)
let yearLabelAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: Element.year.kind, with: yearLabelIndexPath)
yearLabelAttributes.frame = CGRect(x: startYearX, y: collectionViewStartY, width: CGFloat(30), height: yearHeight)
cache[.year]?[yearLabelIndexPath] = yearLabelAttributes
yearIndex += 1
}
date = Calendar.current.date(byAdding: .month, value: 1, to: date)!
monthIndex += 1
}
}
}
extension TimelineLayout {
public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
switch elementKind {
case Element.year.kind:
return cache[.year]?[indexPath]
default:
return cache[.month]?[indexPath]
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
visibleLayoutAttributes.removeAll(keepingCapacity: true)
if let yearAttrs = cache[.year], let monthAttrs = cache[.month], let dayAttrs = cache[.day] {
for (_, attributes) in yearAttrs {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
for (_, attributes) in monthAttrs {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
for (_, attributes) in dayAttrs {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
// visibleLayoutAttributes.append(self.shiftedAttributes(from: attributes))
}
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = cache[.day]?[indexPath] else { fatalError("No attributes cached") }
return attributes
// return shiftedAttributes(from: attributes)
}
override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if oldBounds.size != newBounds.size {
cache.removeAll(keepingCapacity: true)
}
return true
}
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
if context.invalidateDataSourceCounts { cache.removeAll(keepingCapacity: true) }
super.invalidateLayout(with: context)
}
}
As you can see I do create timelineDataSource supplementary View Provider in the view controller. Then in the Timeline Layout I implement layoutAttributesForElements(in rect: CGRect) and layoutAttributesForSupplementaryView(ofKind .... The latter never gets called - the error comes first. layoutAttributesForElements(in rect: CGRect) does get called and filled with dayAttrs, monthAttrs, and yearAttrs. However still, the error occurs afterwards:
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: self.supplementaryViewProvider || (self.supplementaryReuseIdentifierProvider && self.supplementaryViewConfigurationHandler)'
terminating with uncaught exception of type NSException
What am I missing?
Edit: I want to add the detail that when I put a breakpoint in that supplementaryViewProvider it's never called. So maybe I am doing something wrong in layoutAttributesForElements(in Rect?)

Swift: Notification permissions pop up affecting class functionality

This is a hard one to explain as I'm not entirely sure what's going wrong here. In my AppDelegate I add an array of services to the app:
override var services: [ApplicationService] {
if UIApplication.isRunningUnitTests() {
return []
}
return [
SettingsService(),
TrackingApplicationService(),
NotificationApplicationService(),
AudioApplicationService(),
ConnectionService()
]
}
The connection service is the one I am interested in here. It looks like this:
import PluggableAppDelegate
import Connectivity
final class ConnectionService: NSObject, ApplicationService {
// MARK: - Helpers
private struct Constants {
static let containerHeight: CGFloat = 20
static let containerPadding: CGFloat = 20
static let statusCenterAdjustment: CGFloat = -1
static let statusFont: UIFont = .systemFont(ofSize: 15, weight: .semibold)
static let labelLines = 1
static let radiusDenominator: CGFloat = 2.0
static let animationDuration = 0.2
static let defaultAnchorConstant: CGFloat = 0
}
internal enum ConnectionStatus {
case connected
case disconnected
}
// MARK: - Properties
private let connectivity = Connectivity()
private let queue = DispatchQueue.global(qos: .utility)
internal var currentStatus = ConnectionStatus.connected
private var statusTopAnchor: NSLayoutConstraint?
private lazy var statusContainer: UIView = {
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
container.backgroundColor = UIColor.red
container.layer.cornerRadius = Constants.containerHeight / Constants.radiusDenominator
container.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
container.isHidden = true
container.addSubview(statusLabel)
NSLayoutConstraint.activate([
statusLabel.centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: Constants.statusCenterAdjustment),
statusLabel.leftAnchor.constraint(equalTo: container.leftAnchor, constant: Constants.containerPadding),
statusLabel.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -Constants.containerPadding)
])
return container
}()
private lazy var statusLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = Constants.labelLines
label.font = Constants.statusFont
label.textAlignment = .center
label.textColor = .white
label.text = Strings.common.user.noInternetConnection.localized
return label
}()
// MARK: - Lifecycle
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
DispatchQueue.main.async { [weak self] in
self?.prepareUI()
}
prepareConnectivityListener()
updateConnectionStatus(connectivity.status)
return true
}
// MARK: - Connectivity
func updateConnectionStatus(_ status: Connectivity.Status) {
switch status {
case .connected, .connectedViaWiFi, .connectedViaCellular:
updateLabelWith(connectionStatus: .connected)
case .notConnected, .connectedViaWiFiWithoutInternet, .connectedViaCellularWithoutInternet:
updateLabelWith(connectionStatus: .disconnected)
}
NotificationCenter.default.post(name: .connectivityStatusChanged, object: self.currentStatus)
}
// MARK: - Configuration
private func prepareUI() {
guard let window = UIApplication.shared.keyWindow else {
assertionFailure("We require a Window")
return // Don't setup in release if we hit this, no point
}
window.addSubview(statusContainer)
statusTopAnchor = statusContainer.topAnchor.constraint(equalTo: window.topAnchor, constant: -Constants.containerHeight)
guard let statusTopAnchor = statusTopAnchor else {
assertionFailure("Top anchor could not be build")
return
}
NSLayoutConstraint.activate([
statusTopAnchor,
statusContainer.centerXAnchor.constraint(equalTo: window.centerXAnchor),
statusContainer.heightAnchor.constraint(equalToConstant: Constants.containerHeight)
])
}
private func prepareConnectivityListener() {
let connectivityChanged: (Connectivity) -> Void = { [weak self] connectivity in
self?.updateConnectionStatus(connectivity.status)
}
connectivity.whenConnected = connectivityChanged
connectivity.whenDisconnected = connectivityChanged
connectivity.startNotifier(queue: queue)
}
private func updateLabelWith(connectionStatus: ConnectionStatus) {
if currentStatus == connectionStatus {
return // No need to update if we are the same state
} else {
currentStatus = connectionStatus
}
let topAnchorConstant: CGFloat = (connectionStatus == .connected) ? -Constants.containerHeight : Constants.defaultAnchorConstant
DispatchQueue.main.async { [weak self] in
DispatchQueue.main.async { [weak self] in
if connectionStatus != .connected {
self?.statusContainer.isHidden = false
}
self?.statusTopAnchor?.constant = topAnchorConstant
UIView.animate(withDuration: Constants.animationDuration,
animations: { [weak self] in
self?.statusContainer.superview?.layoutIfNeeded()
},
completion: { _ in
if connectionStatus == .connected {
self?.statusContainer.isHidden = true
}
})
}
}
}
}
So I'm using this service to check the connection status of the device and on the login view controller I display different warnings depending on the connection status.
However, my problem is is that the connection service is not functioning properly when the permissions pop up appears. As you can see from the ConnectionService class, an alert should be generated when the app is offline. Now because I am adding this connection service before the permissions are requested, the window briefly shows the alert, but as soon as the permissions alert appears, it disappears.
Furthermore, you can see in ConnectionService.swift I have a listener set up:
NotificationCenter.default.post(name: .connectivityStatusChanged, object: self.currentStatus)
I'm listening for this in the subsequent viewController which triggers this method:
#objc private func connectivityChanged(notification: Notification) {
if notification.object as? ConnectionService.ConnectionStatus == .connected {
deviceConnected = true
} else {
deviceConnected = false
}
}
This in turn triggers a UI change:
private var deviceConnected: Bool = true {
willSet {
DispatchQueue.main.async {
if newValue == true {
self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_completed")
self.errorLabel?.isHidden = true
} else {
self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_offline")
self.errorLabel?.isHidden = false
self.errorLabel?.text = Strings.eHandshake.warning.deviceOffline.title.localized
}
}
}
}
When permissions have already been granted and therefore the app launches without requesting permission, this works fine, but when the permissions alert appears, this seems to break this part of the logic.
To be clear the services var is initially declared here:
public protocol ApplicationService: UIApplicationDelegate {}
extension ApplicationService {
public var window: UIWindow? {
return UIApplication.shared.delegate?.window ?? nil
}
}
open class PluggableApplicationDelegate: UIResponder,
UIApplicationDelegate {
public var window: UIWindow?
open var services: [ApplicationService] { return [] }
internal lazy var _services: [ApplicationService] = {
return self.services
}()
#discardableResult
internal func apply<T, S>(_ work: (ApplicationService, #escaping (T) -> Void) -> S?, completionHandler: #escaping ([T]) -> Swift.Void) -> [S] {
let dispatchGroup = DispatchGroup()
var results: [T] = []
var returns: [S] = []
for service in _services {
dispatchGroup.enter()
let returned = work(service, { result in
results.append(result)
dispatchGroup.leave()
})
if let returned = returned {
returns.append(returned)
} else { // delegate doesn't implement method
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completionHandler(results)
}
return returns
}
}
Any help with this would be much appreciated.

Custom alert presenting over empty window instead of previous viewController

As you can see I am setting the presentation style as .overCurrentContext.
extension SingleQuestionViewController: AddResponseDelegate {
func save(response text: String, questionID: Int) {
questionsWrapper.add(newResponse: text, questionID: questionID) { [weak self] successful in
if successful {
self?.responseTV?.safelyReload()
} else {
DispatchQueue.main.async {
let alertViewController = AlertViewController<Any>()
alertViewController.modalPresentationStyle = .overCurrentContext
let contentModel = RegularContentsModel(title: "controllerTitle", message: "message")
let authorizeButtonModel = SimpleButtonModel(title: "yes message", action: {
//action goes here
})
let doNothingButtonModel = SimpleButtonModel(title: "noMsg", action: {
//completion?()
})
alertViewController.styleRegular(regularContentsModel: contentModel,
models: [authorizeButtonModel, doNothingButtonModel])
self?.present(alertViewController, animated: false, completion: nil)
}
}
}
questionsWrapper.goToQuestion(with: questionID)
responseTV?.safelyReload()
}
}
Here is the result:
I don't think this is caused by it being on the background thread, because if I move it to viewDidLoad, then I get the same result:
override func viewDidLoad() {
super.viewDidLoad()
setUpTopBar()
setupSearchBar()
responseTV.showsVerticalScrollIndicator = false
setupArrows()
responseTV.register(SimpleCell.self, forCellReuseIdentifier: SimpleCell.reuseID)
setAccessibility()
let alertViewController = AlertViewController<Any>()
alertViewController.modalPresentationStyle = .overCurrentContext
let contentModel = RegularContentsModel(title: "controllerTitle", message: "message")
let authorizeButtonModel = SimpleButtonModel(title: "yes message", action: {
//action goes here
})
let doNothingButtonModel = SimpleButtonModel(title: "noMsg", action: {
//completion?()
})
alertViewController.styleRegular(regularContentsModel: contentModel,
models: [authorizeButtonModel, doNothingButtonModel])
self.present(alertViewController, animated: false, completion: nil)
}
Here is the implementation of my custom alert.
class AlertViewController<Payload>: AkinVC {
typealias FlagsAction = ([ReportFlag], Payload) -> Void
enum AlertStyle<Payload> {
case flag(FlagsAction)
}
let innerWholeAlertContainer = UIView()
let outerWholeAlertContainer = UIView()
let buttonStack = AlertButtonsStack()
var payload: Payload?
let transitionDuration: TimeInterval = 0.11
let containerWidth: CGFloat = 300
private var contentsView: UIView! {
didSet {
innerWholeAlertContainer.addSubview(contentsView)
contentsView.constraints(firstHorizontal: .distanceToLeading(innerWholeAlertContainer.leadingAnchor, 0),
secondHorizontal: .distanceToTrailing(innerWholeAlertContainer.trailingAnchor, 0),
vertical: .distanceToTop(innerWholeAlertContainer.topAnchor, 0),
secondVertical: .distanceToBottom(buttonStack.topAnchor, 0))
}
}
func styleNoButtons(regularContentsModel: RegularContentsModel) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
setButtonConstraints()
}
func styleAsFlagView(flagsAction: #escaping FlagsAction) {
initialSetup()
let stackView = FlagsStackView()
stackView.flagItemViews = [FlagItemView](ReportFlag.allCases)
contentsView = stackView
buttonStack.buttonModels(
ButtonModel(tekt: "Report", color: .romanceRed, tektColor: .white, action: { [weak stackView] in
guard let selectedFlags = stackView?.flagItemViews?.selectedFlags,
let payload = self.payload else { return }
flagsAction(selectedFlags, payload)
self.dismissAlert()
}),
ButtonModel(tekt: "Cancel", color: .white, tektColor: .black,
borders: BorderModel(color: UIColor.black.withAlphaComponent(0.16), width: 1, edges: [.top]),
action: { [weak self] in
self?.dismissAlert()
})
)
setButtonConstraints()
}
func styleAsOkayAlert(regularContentsModel: RegularContentsModel, action: Action? = nil) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
let okayModel = standardizeButtonsWithDismissAction(models: [SimpleButtonModel(title: "Okay, I got it.", action: action)])
buttonStack.buttonModels(okayModel)
setButtonConstraints()
}
func styleCancelAlert(regularContentsModel: RegularContentsModel, models: SimpleButtonModel...) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
var models = models
models.append(SimpleButtonModel(title: "Cancel"))
let newButtonModels = standardizeButtonsWithDismissAction(models: models)
buttonStack.buttonModels(newButtonModels)
setButtonConstraints()
}
func styleRegular(regularContentsModel: RegularContentsModel, models: SimpleButtonModel...) {
self.styleRegular(regularContentsModel: regularContentsModel, models: models)
}
func styleRegular(regularContentsModel: RegularContentsModel, models: [SimpleButtonModel]) {
initialSetup()
let alertContentView = RegularContentsView()
alertContentView.model = regularContentsModel.forContainer(width: containerWidth)
contentsView = alertContentView
let newButtonModels = standardizeButtonsWithDismissAction(models: models)
buttonStack.buttonModels(newButtonModels)
setButtonConstraints()
}
private func standardizeButtonsWithDismissAction(models: [SimpleButtonModel]) -> [ButtonModel] {
var buttonModelsToAdd: [ButtonModel] = []
let count = models.count
for (inde, model) in models.enumerated() {
var borders: [BorderModel] = []
if count > 2 || count == 1 {
borders.append(BorderModel(color: .lightGray, width: 1, edges: [.top]))
} else if count == 2 {
if inde == 0 {
borders.append(BorderModel(color: .lightGray, width: 1, edges: [.top]))
} else if inde == 1 {
borders.append(BorderModel(color: .lightGray, width: 1, edges: [.left, .top]))
}
}
buttonModelsToAdd.append(ButtonModel(tekt: model.title, color: .white, tektColor: .darkGray, borders: borders, action: {
model.action?()
self.dismissAlert()
}))
}
return buttonModelsToAdd
}
func dismissAlert() {
UIView.animate(withDuration: transitionDuration, animations: {
self.view.alpha = 0
}) { (completed) in
self.safelyDissmiss(animated: false)
}
}
fileprivate func initialSetup() {
self.view.alpha = 0
modalPresentationStyle = .currentContext
view.backgroundColor = UIColor.black.withAlphaComponent(0.3)
view.addSubview(outerWholeAlertContainer)
outerWholeAlertContainer.addSubview(innerWholeAlertContainer)
outerWholeAlertContainer.pinToEdges(innerWholeAlertContainer)
innerWholeAlertContainer.backgroundColor = .white
innerWholeAlertContainer.addSubview(buttonStack)
outerWholeAlertContainer.constraints(.horizontal(.centeredHorizontallyWith(view)),
.vertical(.centeredVerticallyTo(view)),
.horizontal(.width(containerWidth)) )
}
func setButtonConstraints() {
buttonStack.constraints(.horizontal(.distanceToLeading(innerWholeAlertContainer.leadingAnchor, 0)),
.horizontal(.distanceToTrailing(innerWholeAlertContainer.trailingAnchor, 0)),
.vertical(.distanceToBottom(innerWholeAlertContainer.bottomAnchor, 0)))
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
outerWholeAlertContainer.layer.applySketchShadow()
innerWholeAlertContainer.roundCorners(constant: 15)
UIView.animate(withDuration: transitionDuration, animations: {
self.view.alpha = 1
})
}
}
Here is what the visual debugger shows:
The view controller:
creates the alert controller;
sets modalPresentationStyle to .overCurrentContext; and
and calls styleRegular.
But styleRegular:
calls initialSetup
which resets modalPresentationStyle to .currentContext
It's that last step that is discarding your previous .overCurrentContext setting.