“I’m encountering a challenge with my Swift app where I call the function getCryptoPrices upon pressing the ‘trackBtn,’ intending to fetch API updates every minute. However, I notice that the current_price only updates after the first minute, and subsequently, it refreshes every 11-12 minutes. I’ve set the timer for 1 minute, and I’d like to understand how I can ensure that the current_price updates every minute consistently. Any insights or suggestions on resolving this issue would be greatly appreciated. Here’s a snippet of my code for reference:
I have my model class:
struct CryptoPrices: Decodable {
var current_price: Double?
mutating func update(current_price: Double) -> CryptoPrices {
self.current_price = current_price
return self
}
}
CryptoManager class, where i store the business logic of the app:
import Combine
import Alamofire
class CryptoManager {
static let shared = CryptoManager()
var timer: Timer?
private var cancellables: Set<AnyCancellable> = []
var currentCryptoPrice: CryptoPrices?
func getCryptoPrices(symbol: String) -> AnyPublisher<CryptoPrices, Error> {
let url = "https://example.com/api/v3/coins/markets?vs_currency=usd&ids=\(symbol)&order=market_cap_desc&per_page=100&page=1&sparkline=false&locale=en"
var urlRequest = URLRequest(url: URL(string: url)!)
urlRequest.httpMethod = HTTPMethod.get.rawValue
urlRequest.cachePolicy = .reloadIgnoringLocalCacheData
return AF.request(urlRequest)
.publishDecodable(type: [CryptoPrices].self, decoder: JSONDecoder())
.tryMap { response in
guard let cryptoPricesArray = response.value else {
throw AFError.responseValidationFailed(reason: .dataFileNil)
}
// Extract the first element from the array
guard let cryptoPrice = cryptoPricesArray.first else {
throw NSError(domain: "Invalid response format", code: 0, userInfo: nil)
}
if let updatedPrice = cryptoPrice.current_price {
return CryptoPrices(current_price: updatedPrice)
} else {
return CryptoPrices(current_price: nil)
}
}
.mapError { $0 as Error }
.eraseToAnyPublisher()
}
func startFetchingWithTimer(symbol: String) -> AnyPublisher<CryptoPrices, Error> {
// Create a Timer publisher that fires every 1 minutes
let timerPublisher = Timer.publish(every: 60, on: .main, in: .common)
.autoconnect()
.map { _ in () }
// Combine the timerPublisher with the fetchCryptoPrices publisher
return timerPublisher
.flatMap { _ in
self.fetchCryptoPrices(symbol: symbol)
.handleEvents(receiveOutput: { cryptoPrices in
print("Received crypto prices: \(cryptoPrices)")
})
}
.eraseToAnyPublisher()
}
private func fetchCryptoPrices(symbol: String) -> AnyPublisher<CryptoPrices, Error> {
getCryptoPrices(symbol: symbol)
.handleEvents(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Error fetching crypto prices: \(error)")
case .finished:
break
}
})
.eraseToAnyPublisher()
}
private var subscriptions = Set<AnyCancellable>()
Here’s my controller:
import UserNotifications
import Combine
@IBOutlet weak var minimumRate: UITextField!
@IBOutlet weak var maximumRate: UITextField!
private let cryptoManager: CryptoManager
private var userNotificationCenter: UNUserNotificationCenter
var symbol: String!
var timer : Timer?
@IBAction func trackBtnPressed(_ sender: UIButton) {
print("Track pressed")
requestNotificationAuthorization()
self.dismiss(animated: true)
}
func startMonitoring(symbol: String, minRate: Double, maxRate: Double) {
guard let minRateText = minimumRate.text, let maxRateText = maximumRate.text else {
return
}
guard let minRate = Double(minRateText), let maxRate = Double(maxRateText) else {
return
}
print("Minimum Rate: \(minRate), Maximum Rate: \(maxRate)")
self.cryptoManager.startFetchingWithTimer(symbol: symbol)
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Error fetching price: \(error)")
case .finished:
break
}
}, receiveValue: { cryptoPrices in
// Now cryptoPrices is of type CryptoPrices
if let currentPrice = cryptoPrices.current_price {
print("Received price: \(currentPrice)")
if currentPrice <= minRate {
print("Min rate:", minRate)
print(currentPrice, "after checking the price has dropped")
self.sendNotification(symbol: symbol, price: currentPrice)
} else if currentPrice >= maxRate {
print("Max rate:", maxRate)
print(currentPrice, "after checking the price has increased")
self.sendNotification(symbol: symbol, price: currentPrice)
} else {
print("Price is the same")
}
}
})
.store(in: &self.subscriptions)
}
func sendNotification(symbol: String, price: Double) {
let content = UNMutableNotificationContent()
content.title = "The price of \(symbol) has changed"
content.body = "Current price: \(price)$"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5.0, repeats: false)
let request = UNNotificationRequest(identifier: "price_alert", content: content, trigger: trigger)
userNotificationCenter.add(request) { error in
if let error = error {
print(error)
}
}
}
private var subscriptions = Set<AnyCancellable>()
func requestNotificationAuthorization() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getNotificationSettings { settings in
switch settings.authorizationStatus {
case .notDetermined:
notificationCenter.requestAuthorization(options: [.alert, .sound]) { didAllow, error in
if didAllow {
self.startMonitoring(
symbol: self.symbol ?? "Not working",
minRate: Double(self.minimumRate.text(in: self.minimumRate.selectedTextRange!) ?? "") ?? 0.0,
maxRate: Double(self.maximumRate.text(in: self.maximumRate.selectedTextRange!) ?? "") ?? 0.0
)
}
}
case .denied:
return
case .authorized:
DispatchQueue.main.async {
self.startMonitoring(
symbol: self.symbol ?? "Not working",
minRate: Double(self.minimumRate.text(in: self.minimumRate.selectedTextRange!) ?? "") ?? 0.0,
maxRate: Double(self.maximumRate.text(in: self.maximumRate.selectedTextRange!) ?? "") ?? 0.0
)
}
default:
return
}
}
}




