Hello I have a question about the MPMusicPlayerController in Swift. I am currently working on a Music App were I want to shuffle music by songs. So when the App Starts it basically sets the Playback Queue and then the Shuffle Mode. I can successfully set the queue (and play the songs) but I get an error when I set the Shuffle Mode:
musicPlayer.musicPlayer.shuffleMode = .songs
ERROR:
2018-07-03 15:01:36.450977+0200 Hitbeat[29053:8378883] [SDKPlayback] -[MPMusicPlayerController setShuffleMode:2] completed error: Error Domain=MPCPlayerRequestErrorDomain Code=1 "No commands provided." UserInfo={NSDebugDescription=No commands provided.}
What does that mean?
I have the idea that it may be because the queue is not set completely when setting the shuffleMode but I am not sure and it would not make any sense that one would have to set a song queue first in order to set the mode in which order songs to play. Maybe something else is the problem?
Also everything takes place on the Main Thread. (MPMusicPlayerController always has to be called in the Main Thread)
Thanks a lot I hope you guys can help me.
here are some code snippets:
MusicPlayerManager.swift
import os.log
import MediaPlayer
import NotificationCenter
class MusicPlayerManager {
let musicPlayer: MPMusicPlayerController
lazy var musicPickerAndAdder = MusicPickerAndAdder()
init() {
// Instantiate a new music player
musicPlayer = MPMusicPlayerApplicationController.applicationQueuePlayer
// Add a playback queue containing all songs on the device
switch MPMediaLibrary.authorizationStatus() {
case .authorized:
let catalogSongStoreID: String = ""
let catalogQueueDescriptor = MPMusicPlayerStoreQueueDescriptor(storeIDs: [catalogSongStoreID])
musicPlayer.setQueue(with: catalogQueueDescriptor)
default:
break
}
Timer.scheduledTimer(withTimeInterval: 15, repeats: false) {_ in
print("shuffle mode setter")
self.musicPlayer.shuffleMode = MPMusicShuffleMode.songs
}
}
func updateOnlineMusicQueue() {
var musicPickerIds = [String]()
DispatchQueue.global(qos: .userInitiated).sync {
musicPickerIds = musicPickerAndAdder.ids
}
if !musicPickerIds.isEmpty{
musicPlayer.setQueue(with: musicPickerIds)
}else {
updateOfflineMusicQueue()
}
musicPlayer.pause()
}
func play() {
if musicPlayer.playbackState == .playing {
musicPlayer.pause()
musicPlayer.skipToBeginning()
}
if !musicPlayer.isPreparedToPlay {
musicPlayer.prepareToPlay { (error) in
if error == nil {
self.musicPlayer.play()
self.startSongMasterTimer()
}
}
}else {
musicPlayer.play()
startSongMasterTimer()
}
}
func pauseAndSkip() {
// if self.musicPlayer.shuffleMode.rawValue != 2 { // does not work here would work on pause and skip
// self.musicPlayer.shuffleMode = MPMusicShuffleMode.songs
// }
//print("shuffler \(self.musicPlayer.shuffleMode.rawValue)")
//print("At \(musicPlayer.currentPlaybackTime) of \((musicPlayer.nowPlayingItem?.playbackDuration!)")
musicPlayer.pause()
//if musicPlayer.nowPlayingItem != nil {
musicPlayer.skipToNextItem()
//}
musicPlayer.prepareToPlay { (error) in
if error == nil {
self.musicPlayer.pause()
}
}
}
func currentSongInfo() -> SongInfo {
let songTitle = musicPlayer.nowPlayingItem?.title?.replacingOccurrences(of: "-", with: " ") ?? "" // To guarantee there is only one - between Song and Artist
let artistName = musicPlayer.nowPlayingItem?.artist?.replacingOccurrences(of: "-", with: " ") ?? ""
let songInfo = SongInfo(title: songTitle, artist: artistName)
return songInfo
}
func addSongToLibrary() {
//print("Id of Item to Add: \(musicPlayer.nowPlayingItem?.playbackStoreID)")
if musicPlayer.nowPlayingItem != nil {
musicPickerAndAdder.addResourceToUserMusicLibrary(resourceId: (musicPlayer.nowPlayingItem?.playbackStoreID)!)
}
//ToDo add to myHitbeat Playlist
}
}
class SongInfo {
let title: String
let artist: String
init(title:String,artist:String) {
self.title = title
self.artist = artist
}
}
MusicPickerAndAdder.swift
import Foundation
class MusicPickerAndAdder {
lazy var authorizationManager: AuthorizationManager = {
return AuthorizationManager(appleMusicManager: self.appleMusicManager)
}()
var appleMusicManager = AppleMusicManager()
private var idsArraySize = 100
static var idCategoriesStakes = ["Chart_Ids" : 0.10,
"Recently_Played_Ids" : 0.10,
"Experiment_Ids" : 0.30,
"Recommendations_Ids" : 0.50,] // Addition of all Values must be 1 (100%)
private var chartIds: [String] {
var chartsIds = [String]()
let chartsIdsGroup = DispatchGroup()
chartsIdsGroup.enter()
let limit = Int(Double(idsArraySize) * MusicPickerAndAdder.idCategoriesStakes["Recently_Played_Ids"]!)
appleMusicManager.performAppleMusicGetChartSongs(regionCode: Locale.current.regionCode?.lowercased() ?? "us", limit: limit) { (storeIds, error) in
if error != nil {
print("There was an Error getting Charts")
chartsIdsGroup.leave()
return
}
chartsIds = storeIds
chartsIdsGroup.leave()
}
chartsIdsGroup.wait()
print("Charts sucessfully fetched")
return chartsIds
}
private var recentlyPlayedIds: [String] {
var recentIds = [String]()
let recentIdsGroup = DispatchGroup()
recentIdsGroup.enter()
let limit = Int(Double(idsArraySize) * MusicPickerAndAdder.idCategoriesStakes["Recently_Played_Ids"]!)
appleMusicManager.performAppleMusicGetRecentlyPlayed(userToken: authorizationManager.userToken, limit: limit) {
(storeIds, error) in
if error != nil {
print("There was an Error getting Recently Played")
recentIdsGroup.leave()
return
}
recentIds = storeIds
recentIdsGroup.leave()
}
recentIdsGroup.wait()
print("Recently Played sucessfully fetched: \(recentIds)")
return recentIds
}
private var experimentIds: [String] {
return ["pl.u-XkD04oZIY0Kxrl"]
}
private var recommendationsIds: [String] {
return [String]()
}
// Never request Ids in Main (UI) Thread
var ids: [String] {
var ids = [String]()
ids += recentlyPlayedIds
ids += chartIds
ids += experimentIds
ids += recommendationsIds
print("Store Ids for Songs \(ids)")
return ids.shuffled() // shuffles list of items
}
init() {
requestAppleMusicAuthorization()
}
//MARK: Private Methods
private func requestAppleMusicAuthorization() {
UserDefaults.standard.register(defaults: ["tutorial": true])
if !UserDefaults.standard.bool(forKey: "tutorial") {
authorizationManager.requestCloudServiceAuthorization()
authorizationManager.requestMediaLibraryAuthorization()
}
}
}
extension MusicPickerAndAdder { // to Add Songs
func addResourceToUserMusicLibrary(resourceId: String) {
appleMusicManager.performAddResourceToLibrary(resourceId: resourceId, userToken: authorizationManager.userToken)
}
}
extension MutableCollection {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
// Change `Int` in the next line to `IndexDistance` in < Swift 4.1
let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
let i = index(firstUnshuffled, offsetBy: d)
swapAt(firstUnshuffled, i)
}
}
}
extension Sequence {
/// Returns an array with the contents of this sequence, shuffled.
func shuffled() -> [Element] {
var result = Array(self)
result.shuffle()
return result
}
}
PS: MusicPickerAndAdder may look a little messy though I don't think the problem lies there! What it basically does is fetching some data from the Apple Music API which works fine, and adding Songs to the User Library which works too.
Okay after trying everything out possible I came up with two solutions that work for me. Weirdly I found out that a freeze of the interface only occurs when no song has played so far. If a song is currently playing or even if a song has played and was paused afterwards there is no ui freeze. So I came up with this function:
private func setShuffleMode() { // does work though startup and restarting takes longer
musicPlayer.play()
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) {_ in
print("shuffle mode setter")
self.musicPlayer.pause()
//self.musicPlayer.pause()// may stop interface freezing if occuring
self.musicPlayer.shuffleMode = MPMusicShuffleMode.songs // freeze of ui only occurs when no song played before
}
}
I tried out several time intervals it still failed sometimes if it was a second it never failed on 1.5 seconds so I left it there
The problem though was that starting the App as well as restarting it was taking a little bit longer. So I came up with a second solution
private func setShuffleMode2 () { // still in test if shuffle mode gets set fast or even ever set
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) {timer in
if self.musicPlayer.playbackState == .playing && self.musicPlayer.currentPlaybackTime > 3{
self.musicPlayer.shuffleMode = .songs
print("shuffle mode setter")
timer.invalidate()
}
}
}
Here I have a repeating timer which is always checking if an item is playing and if it is playing for a certain time already, if it is it sets the shuffle mode and stops repeating. I have tested the second function and it worked great though there is the trade of that there is always the possibility that it is not getting called for some time. How long that will be depends on the time interval and currentPlaybackTime > someTime value.
Related
I'm trying to save player game data using GKSavedGame from GameKit, but it doesn't work, and I don't get any error. (Is the data not saving? Is the data not loading back?)
One of the problem is that GKSavedGame is not well documented (there are no examples like we can find for other things), so we don't really know how to implement it correctly (e.g. what are the best practices?)
I'm using a simple struct GameData: Codable to store the game data, that I encode using JSONEncoder/JSONDecoder. Here is my GameService class with the part that doesn't work:
class GameService {
// Shared instance
static let shared = GameService()
// Properties
private(set) var isGameLoaded = false
private(set) var gameData: GameData?
// Methods
func loadSavedGame(completionHandler: #escaping () -> Void) {
// Check game is not loaded yet
if isGameLoaded {
completionHandler()
return
}
// Get player
let localPlayer = GKLocalPlayer.local
if localPlayer.isAuthenticated {
localPlayer.fetchSavedGames { games, error in
// Iterate saved games
var game: GKSavedGame?
for currentGame in games ?? [] {
if game == nil || game?.modificationDate ?? Date() < currentGame.modificationDate ?? Date() {
game = currentGame
}
}
// If one found, load its data
if let game = game {
game.loadData { data, error in
if let data = data {
self.gameData = try? JSONDecoder().decode(GameData.self, from: data)
}
self.isGameLoaded = true
self.initGameData()
completionHandler()
}
} else {
self.isGameLoaded = true
self.initGameData()
completionHandler()
}
}
}
}
func saveGame() {
// Get player
let localPlayer = GKLocalPlayer.local
if localPlayer.isAuthenticated, let gameData = gameData, let data = try? JSONEncoder().encode(gameData) {
// Save its game data
localPlayer.saveGameData(data, withName: "data") { savedGame, error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
func initGameData() {
// If game data is undefined, define it
if gameData == nil {
gameData = GameData()
}
}
func gameEnded(level: Level, score: Int64) {
// Here I edit my `gameData` object before saving the changes
// I known that this method is called because the data is up to date in the UI
// ...
saveGame()
}
}
Also, GameCenter is enabled in the app capabilities (and workout for other things like leaderboards)
After restarting the app, and calling loadSavedGame, the gameData property is not restored to its previous state.
What is wrong with this code?
Note: I tested it on both Simulator and on my own iPhone (real daily device where GameCenter and iCloud are working with other apps) and it never saves anything.
After enabling iCloud in Capabilities, iCloud Documents, and creating a container for the app, it now works. There is nothing in the documentation about this.
I'm working on a watchOS App as my first Swift/iOS project ever. I want to fetch the latest body weight sample and use it for some calculation. The result is presented to the user. As soon as a new sample is added, I want to update my UI as well. It works in a completely fresh simulator installation. As soon as I add a sample in the iOS simulator, the app updates its UI in the watchOS simulator. However, it doesn't work on my real device or after resetting the watchOS simulator. And I just don't know why. The HKAnchoredObjectQuery just returns 0 samples but I definitely have some samples stored in health. I can even see them under Settings > Health on my watch. I can't imagine this is related to my code, but here it is:
class WeightProvider: ObservableObject {
private static let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass)!
private static let healthStore: HKHealthStore = .init()
private var previousAnchor: HKQueryAnchor?
private var runningQuery: HKAnchoredObjectQuery?
#Published var bodyWeight: Measurement<UnitMass>?
func getBodyWeight(longRunning: Bool = false) {
let query = HKAnchoredObjectQuery(type: Self.weightSampleType, predicate: nil, anchor: previousAnchor, limit: longRunning ? HKObjectQueryNoLimit : 1, resultsHandler: processQueryResult)
if longRunning {
query.updateHandler = processQueryResult
runningQuery = query
}
Self.healthStore.execute(query)
}
func stopLongRunningQuery() {
if let runningQuery = runningQuery {
Self.healthStore.stop(runningQuery)
self.runningQuery = nil
}
}
private func processQueryResult(_: HKAnchoredObjectQuery, samples: [HKSample]?, _: [HKDeletedObject]?, newAnchor: HKQueryAnchor?, error: Error?) {
guard let samples = samples as? [HKQuantitySample], error == nil else {
fatalError(error?.localizedDescription ?? "Failed to cast [HKSample] to [HKQuantitySample]")
}
previousAnchor = newAnchor
guard let sample = samples.last else {
return
}
DispatchQueue.main.async {
if Locale.current.usesMetricSystem {
let weight = sample.quantity.doubleValue(for: .gramUnit(with: .kilo))
self.bodyWeight = .init(value: weight, unit: UnitMass.kilograms)
} else {
let weight = sample.quantity.doubleValue(for: .pound())
self.bodyWeight = .init(value: weight, unit: UnitMass.pounds)
}
}
}
}
// MARK: - HealthKit Authorization
extension WeightProvider {
private static let typesToRead: Set<HKObjectType> = [
weightSampleType,
]
func authorize(completion: #escaping (Bool, Error?) -> Swift.Void) {
Self.healthStore.requestAuthorization(toShare: nil, read: Self.typesToRead) { success, error in
completion(success, error)
}
}
}
In my Views onAppear I call this function:
private func authorizeHealthKit() {
guard firstRun else {
return
}
firstRun = false
weightProvider.authorize { success, error in
guard success, error == nil else {
return
}
weightProvider.getBodyWeight(longRunning: true)
}
}
HealthKit is properly authorized as I can see in the Settings of my Watch. Any ideas? Any tips for my code in general?
Wow, after all this time I found the issue: The line previousAnchor = newAnchor needs to be after the guard statement. That's it.
I am currently writing an analytics system.
Currently, it caches Events in RAM.
It writes to the Filesystem via NSUserDefaults (iOS) and SharedPreferences (Android) when the App closes, as JSON.
This Data is read when the app opens.
It also sends every N seconds or when the amount of Events reaches 20.
When the sending was successful, it deletes all events that were send from the RAM.
This has some obvious flaws: When the app crashes, all data from N seconds is lost. When the server cannot be reached (because Server is down for example) and the app crashes, even more data are lost.
My question here is: How can I improve the "safety" of my data and prevent massive data loss when the server is down or not reachable?
Here is my current code (unimportant parts removed)
import Foundation
class BackendTrackingHandler : TrackingHandler {
static let KEY_CACHE_EVENT = "TrackingCache"
private static let SEND_INTERVAL:TimeInterval = 10
var cachedEvents: [TrackingEvent] = []
var temporaryCachedEvents: [TrackingEvent] = []
var prefix: String
var endpoint: String
var timer : Timer?
//whether we currently wait for a response
var isSending: Bool = false
override init() {
//init
readCachedEventsFromDisk()
timer = Timer.scheduledTimer(timeInterval: BackendTrackingHandler.SEND_INTERVAL, target: self, selector: #selector(send), userInfo: nil, repeats: true)
}
override func trackEvent(_ event: TrackingEvent) {
cachedEvents.append(event)
if((cachedEvents.count) >= 20) {
send()
}
}
#objc func send() {
if((cachedEvents.count) < 1) {
return
}
if(isSending) {
return
}
isSending = true
let enc = JSONEncoder()
enc.outputFormatting = .prettyPrinted
let data = try! enc.encode(cachedEvents)
// Constructring Request here
let session = URLSession.shared
//while the request is on the way, we can trigger new events. Make a temporary copy
temporaryCachedEvents = cachedEvents
let taksID = UIApplication.shared.beginBackgroundTask()
let task = session.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if(error != nil)
{
self.isSending = false
UIApplication.shared.endBackgroundTask(taksID)
}else {
let httpResponse = response as! HTTPURLResponse
if(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299) {
//success, Data was sent so we can create a new cached event
//remove all events we already sent
self.cachedEvents = self.cachedEvents.filter{!self.temporaryCachedEvents.contains($0)}
self.isSending = false
UIApplication.shared.endBackgroundTask(taksID)
}else {
self.isSending = false
UIApplication.shared.endBackgroundTask(taksID)
}
}
}
task.resume()
}
func readCachedEventsFromDisk() {
let dec = JSONDecoder()
guard let data = UserDefaults.standard.data(forKey: BackendTrackingHandler.KEY_CACHE_EVENT) else {
cachedEvents = []
return
}
do {
cachedEvents = try dec.decode([TrackingEvent].self, from: data)
} catch {
cachedEvents = []
}
}
func writeCachedEventsToDisk() {
let enc = JSONEncoder()
let data = try! enc.encode(cachedEvents)
UserDefaults.standard.set(data, forKey: BackendTrackingHandler.KEY_CACHE_EVENT)
}
override func onApplicationBecomeActive() {
}
override func onApplicationBecomeInactive() {
let taskID = UIApplication.shared.beginBackgroundTask()
writeCachedEventsToDisk()
UIApplication.shared.endBackgroundTask(taskID)
}
}
€dit:
TrackingEvent is a struct that is shared among multiple TrackingHandlers. There is an additional FirebaseTrackingHandler, which is meant to be operated side-by-side our own analytics system.
I think the easiest way is writing "Property Wrapper" for cachedEvents so it would directly access to UserDefaults, it seems the operation is not so huge to bother.
Second way - you could simply save cache to UserDefaults every N seconds/minutes or so if you care about performance a lot. Though, it wouldn't made your system bulletproof
I'm pretty new to IOS Application Development.
I'm trying to stop viewWillAppear from finishing until after my function has finished working. How do I do that?
Here's viewWillAppear:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
checkFacts()
if reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
func checkFacts() {
let date = getDate()
var x: Bool = true
var ind: Int = 0
print("count is ", birdFacts.count)
while ind < birdFacts.count {
print("accessing each bird fact in checkFacts")
let imageAsset: CKAsset = birdFacts[ind].valueForKey("birdPicture") as! CKAsset
let image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
print(image)
if image == nil {
if (birdFacts[ind].valueForKey("sortingDate") != nil){
print("replacing fact")
print("accessing the sortingDate of current fact in checkFacts")
let sdate = birdFacts[ind].valueForKey("sortingDate") as! NSNumber
replaceFact(sdate, index: ind)
}
/*else {
birdFacts.removeAll()
print("removing all bird facts")
}*/
}
ind = ind + 1
print(ind)
}
self.saveFacts()
let y = checkRepeatingFacts()
if y {
print("removing all facts")
birdFacts.removeAll()
//allprevFacts(date, olddate: 0)
}
}
checkFacts references 2 others functions, but I'm not sure they're relevant here (but I will add them in if they are and I'm mistaken)
Instead of trying to alter or halt the application's actual lifecycle, why don't you try using a closure?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
checkFacts(){ Void in
if self.reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
}
func checkFacts(block: (()->Void)? = nil) {
let date = getDate()
var x: Bool = true
var ind: Int = 0
print("count is ", birdFacts.count)
while ind < birdFacts.count {
print("accessing each bird fact in checkFacts")
let imageAsset: CKAsset = birdFacts[ind].valueForKey("birdPicture") as! CKAsset
let image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
print(image)
if image == nil {
if (birdFacts[ind].valueForKey("sortingDate") != nil){
print("replacing fact")
print("accessing the sortingDate of current fact in checkFacts")
let sdate = birdFacts[ind].valueForKey("sortingDate") as! NSNumber
replaceFact(sdate, index: ind)
}
/*else {
birdFacts.removeAll()
print("removing all bird facts")
}*/
}
ind = ind + 1
print(ind)
}
self.saveFacts()
let y = checkRepeatingFacts()
if y {
print("removing all facts")
birdFacts.removeAll()
//allprevFacts(date, olddate: 0)
}
// CALL CODE IN CLOSURE LAST //
if let block = block {
block()
}
}
According to Apple Documentation:
Closures are self-contained blocks of functionality that can be passed around and used in your code.
Closures can capture and store references to any constants and variables from the context in which they are defined.
So by defining checkFacts() as: func checkFacts(block: (()->Void)? = nil){...} we can optionally pass in a block of code to be executed within the checkFacts() function.
The syntax block: (()->Void)? = nil means that we can take in a block of code that will return void, but if nothing is passed in, block will simply be nil. This allows us to call the function with or without the use of a closure.
By using:
if let block = block {
block()
}
we can safely call block(). If block comes back as nil, we pass over it and pretend like nothing happened. If block is not nil, we can execute the code contained within it, and go on our way.
One way we can pass our closure code into checkFacts() is by means of a trailing closure. A trailing closure looks like this:
checkFacts(){ Void in
if self.reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
Edit: Added syntax explanation.
So based on the comments, checkFacts is calling asynchronous iCloud operations that if they are not complete will result in null data that your view cannot manage.
Holding up viewWillAppear is not the way to manage this - that will just result in a user interface delay that will irritate your users.
Firstly, your view should be able to manage null data without crashing. Even when you solve this problem there may be other occasions when the data becomes bad and users hate crashes. So I recommend you fix that.
To fix the original problem: allow the view to load with unchecked data. Then trigger the checkData process and when it completes post an NSNotification. Make your view watch for that notification and redraw its contents when it occurs. Optionally, if you don't want your users to interact with unchecked data: disable appropriate controls and display an activity indicator until the notification occurs.
when i ran this code, update labels (aLabel and bLabel) perfectly. the viewDidLoad run perfectly, but when i press freshButtonPressed the Xcode show error. the unexpectedly found nil while unwrapping an Optional value appeared on self.aLabel.text = var1 (comment shows the error place).
the code of viewDidLoad is same as freshButtonPressed. I couldn't figure out the problem.
I appriciated.
import UIKit
class segView1ViewController: UIViewController {
#IBOutlet weak var dollarUSALabel: UILabel!
#IBOutlet weak var euroLabel: UILabel!
func refreshingData1() {
println("refreshing ...")
var dollar2 = ""
var euro2 = ""
var arzUrl = NSURL(string: "http://www.arzlive.com")
if arzUrl != nil {
let task2 = NSURLSession.sharedSession().dataTaskWithURL(arzUrl!, completionHandler: { (data, response, error) -> Void in
var urlError = false
if error == nil {
//main code
//parsing url contents
var arzUrlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
//println(arzUrlContent)
/////////// Dollar
var dollarTempArray = arzUrlContent.componentsSeparatedByString("s3_40 price\">")
//println(dollarTempArray[1])
if dollarTempArray.count > 0 {
var dollarTempArray2 = dollarTempArray[1].componentsSeparatedByString("<td")
dollar2 = dollarTempArray2[0] as! String
//println(dollar)
} else {
urlError = true
}
////////// Euro
var euroTempArray = arzUrlContent.componentsSeparatedByString("s3_41 price\">")
//println(euroTempArray[1])
if euroTempArray.count > 0 {
var euroTempArray2 = euroTempArray[1].componentsSeparatedByString("<td")
euro2 = euroTempArray2[0] as! String
//println(euro)
} else {
urlError = true
}
} else {
//error handling for web task error
urlError = true
}
dispatch_async(dispatch_get_main_queue()) {
//checking error
if urlError == true {
//run error func
self.showError()
} else {
//update labels here
self.dollarUSALabel.text = dollar2
self.euroLabel.text = euro2
}
}
})
//resume task
task2.resume()
} else {
// error handler
showError()
}
////////end of func
}
func showError() {
//some error handling code...
}
override func viewDidLoad() {
super.viewDidLoad()
self.dollarUSALabel.text = "0"
self.euroLabel.text = "0"
var dollar = ""
var euro = ""
// arzlive url
var arzUrl = NSURL(string: "http://www.arzlive.com")
if arzUrl != nil {
let task1 = NSURLSession.sharedSession().dataTaskWithURL(arzUrl!, completionHandler: { (data, response, error) -> Void in
var urlError = false
if error == nil {
//main code
//parsing url contents
var arzUrlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
//println(arzUrlContent)
/////////// Dollar
var dollarTempArray = arzUrlContent.componentsSeparatedByString("s3_40 price\">")
//println(dollarTempArray[1])
if dollarTempArray.count > 0 {
var dollarTempArray2 = dollarTempArray[1].componentsSeparatedByString("<td")
dollar = dollarTempArray2[0] as! String
//println(dollar)
} else {
urlError = true
}
////////// Euro
var euroTempArray = arzUrlContent.componentsSeparatedByString("s3_41 price\">")
//println(euroTempArray[1])
if euroTempArray.count > 0 {
var euroTempArray2 = euroTempArray[1].componentsSeparatedByString("<td")
euro = euroTempArray2[0] as! String
//println(euro)
} else {
urlError = true
}
} else {
//error handling for web task error
urlError = true
}
dispatch_async(dispatch_get_main_queue()) {
//checking error
if urlError == true {
//run error func
self.showError()
} else {
//update labels here
self.dollarUSALabel.text = dollar
self.euroLabel.text = euro
}
}
})
//resume task
task1.resume()
} else {
// error handler
showError()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Ran the project. Could not reproduce the problem. Two issues though:
-- Your code doesn't compile because the dollarTempArrays are not declared anywhere. Please submit something that compiles.
-- You have an array indexing logic error, because you're checking if the count is at least one, but then indexing the second element (at index 1, not index 0, where you should be checking)
Please submit a version that compiles and ideally a website that provides the data. Although, I hardcoded values in place of var1 and var2 and the UI updated no problem.
Since your crash is out of sync with your posted code, I'll bet other things are out of sync in your environment. Quit Xcode & iOS Simulator, get back in, do a clean build, verify your Outlets, and post what you really have now.
Calling your variables var12345 is both sadistic and masochistic, so start abandoning that practice now, even in test code. It's much harder to help with that kind of blatant violations of good practice.
** LATER THAT DAY **
I put it through the professional programmer descrambler and the following resulted. What you're trying to do is not complicated and need not be long. Please observe style, abstraction, efficiency, as well as bug fixes. I just tested this in Xcode 6.4 / Swift 1.2 / iOS simulator on iPhone 6.
import UIKit
class CurrencyViewController: UIViewController {
#IBOutlet weak var dollarUSALabel: UILabel!
#IBOutlet weak var euroLabel: UILabel!
let dataURL = "http://www.arzlive.com"
func refreshData() {
println("refreshing ...")
if let arzUrl = NSURL(string: dataURL) {
let task2 = NSURLSession.sharedSession().dataTaskWithURL(arzUrl)
{ [unowned self]
(data, response, error) -> Void in
if let d = data, arzUrlContent = NSString(data: d, encoding: NSUTF8StringEncoding) {
var dollar: String?
var euro: String?
let dollarTempArray = arzUrlContent.componentsSeparatedByString("s3_40 price\">")
if dollarTempArray.count >= 2 {
let dollarTempArray2 = dollarTempArray[1].componentsSeparatedByString("<td")
dollar = dollarTempArray2[0] as? String
}
let euroTempArray = arzUrlContent.componentsSeparatedByString("s3_41 price\">")
if euroTempArray.count >= 2 {
var euroTempArray2 = euroTempArray[1].componentsSeparatedByString("<td")
euro = euroTempArray2[0] as? String
}
}
dispatch_async(dispatch_get_main_queue()) {
self.dollarUSALabel.text = dollar ?? "Could not get dollars!"
self.euroLabel.text = euro ?? "Could not get euros!"
}
}
task2.resume() // START task ('resume' is misleading)
}
else {
showError("Could not access \(dataURL)")
}
} /* end of refreshData */
/* TODO for you: Use UIAlertController to inform the user */
func showError(message: String) {
println(message)
}
override func viewDidLoad() {
super.viewDidLoad()
refreshData()
}
}