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()
}
}
Related
So I have this function in class Functions :
struct Prices {
var standardPrice: Int!
}
// FUNC PRICING
class Functions {
private var PricingRef: CollectionReference!
var price = Prices()
func getPrice() -> Prices {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
}
}
}
return price
}
}
Then I want to pass the standardPrice value to this class, called PriceList :
class PriceList: UITableViewController {
var price = Prices()
var newStandardPrice = 0
func Price() {
price = Functions().getPrice()
newStandardPrice = price.standardPrice // always error with value nil
}
I always have that error where newStandardPrice is nil.
but the print(self.price.standardPrice!) shows number of result I want.
So as far as I know, the problem here is because it takes time for the firebase firestore to get the data from database.
How do I get the value of standardPrice after its assigned with the new price from firebase database?
Any help will be appreciated
Thankyou
you need to use completion handler because its async function
func getPrice(completion:#escaping (Prices?,Error?)-> Void) {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
completion(nil,err)
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
completion(self.price.standardPrice,nil)
}
}
}
}
How to use
Functions().getPrice { (price, error) in
if let err = error {
// do something if you get error
} else if let getPrice = price {
// use price
self.price = getPriice
}
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.
I am beginner in programming. I actually have my own answer of this questions and the app worked as I am expected, but I am not sure if this is the correct way to to this.
This check out action will be triggered after the user click chechoutButton. but before before this chechoutButton.isEnabled , I have to make sure 3 parameters are available (not nil). before doing this check out action, I need 3 parameters :
get user's coordinate from GPS.
get user's location address from Google Place
API
Get current date time from server for verification.
method to get user location address from Google Place API will be triggered only if I get the coordinate from GPS, and as we know, fetching data from the internet (to take date and time) also takes time, it should be done asynchronously.
how do I manage this checkoutButton only enabled if those 3 parameters are not nil ? Is there a better way according to apple guideline to do this
the simplified code are below
class CheckoutTVC: UITableViewController {
#IBOutlet weak var checkOutButton: DesignableButton!
var checkinAndCheckoutData : [String:Any]? // from MainMenuVC
var dateTimeNowFromServer : String?
var userLocationAddress : String?
let locationManager = LocationManager()
var coordinateUser : Coordinate? {
didSet {
getLocationAddress()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// initial state
checkOutButton.alpha = 0.4
checkOutButton.isEnabled = false
getDateTimeFromServer()
getCoordinate()
}
#IBAction func CheckoutButtonDidPressed(_ sender: Any) {
}
}
extension CheckoutTVC {
func getDateTimeFromServer() {
activityIndicator.startAnimating()
NetworkingService.getDateTimeFromServer { (result) in
switch result {
case .failure(let error) :
self.activityIndicator.stopAnimating()
// show alert
case .success(let timeFromServer) :
let stringDateTimeServer = timeFromServer as! String
self.dateTimeNowFromServer = stringDateTimeServer
self.activityIndicator.stopAnimating()
}
}
}
func getCoordinate() {
locationManager.getPermission()
locationManager.didGetLocation = { [weak self] userCoordinate in
self?.coordinateUser = userCoordinate
self?.activateCheckOutButton()
}
}
func getLocationAddress() {
guard let coordinateTheUser = coordinateUser else {return}
let latlng = "\(coordinateTheUser.latitude),\(coordinateTheUser.longitude)"
let request = URLRequest(url: url!)
Alamofire.request(request).responseJSON { (response) in
switch response.result {
case .failure(let error) :// show alert
case .success(let value) :
let json = JSON(value)
let locationOfUser = json["results"][0]["formatted_address"].string
self.userLocationAddress = locationOfUser
self.locationAddressLabel.text = locationOfUser
self.activateNextStepButton()
}
}
}
func activateCheckoutButton() {
if dateTimeNowFromServer != nil && userLocationAddress != nil {
checkOutButton.alpha = 1
checkOutButton.isEnabled = true
}
}
}
I manage this by using this method, but I don't know if this is the correct way or not
func activateCheckoutButton() {
if dateTimeNowFromServer != nil && userLocationAddress != nil {
checkOutButton.alpha = 1
checkOutButton.isEnabled = true
}
}
You can use DispatchGroup to know when all of your asynchronous calls are complete.
func notifyMeAfter3Calls() {
let dispatch = DispatchGroup()
dispatch.enter()
API.call1() { (data1)
API.call2(data1) { (data2)
//DO SOMETHING WITH RESPONSE
dispatch.leave()
}
}
dispatch.enter()
API.call3() { (data)
//DO SOMETHING WITH RESPONSE
dispatch.leave()
}
dispatch.notify(queue: DispatchQueue.main) {
finished?(dispatchSuccess)
}
}
You must have an equal amount of enter() and leave() calls. Once all of the leave() calls are made, the code in DispatchGroupd.notify will be called.
I am following Stanford's cs193P "Developing IOS 9 Apps with swift" course. I'm working on assignment-4 of the lecture. The app fetches the tweets, which include the word you search, from twitter.
I try to change the colour of words which start with special characters(#,# or http).
When I compile the app it succeeds to change colours and the app runs without problem. But then when I scroll down and my app tries to show new tweets it crashes giving the following error.
Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds'
I am using Xcode 7.3.1 with swift 2.2
I searched both on Stackoverflow and google and found the following similar questions
Question-1
Question-2
Question-3
Question-4
In the questions above problems were related to the length of the range or misusing the NSRange related to its length.
In my case I don't see any problem related to length. You can click to view my whole project in Github
public class Mention: NSObject
{
public var keyword: String // will include # or # or http prefix
public var nsrange: NSRange // index into an NS[Attributed]String made from the Tweet's text
public override var description: String { return "\(keyword) (\(nsrange.location), \(nsrange.location+nsrange.length-1))" }
init?(fromTwitterData data: NSDictionary?, inText text: NSString, withPrefix prefix: String)
{
guard
let indices = data?.valueForKeyPath(Tweet.TwitterKey.Entities.Indices) as? NSArray,
let start = (indices.firstObject as? NSNumber)?.integerValue where start >= 0,
let end = (indices.lastObject as? NSNumber)?.integerValue where end > start
else {
return nil
}
var prefixAloneOrPrefixedMention = prefix
if let mention = data?.valueForKeyPath(Tweet.TwitterKey.Entities.Text) as? String {
prefixAloneOrPrefixedMention = mention.prependPrefixIfAbsent(prefix)
}
let expectedRange = NSRange(location: start, length: end - start)
guard
let nsrange = text.rangeOfSubstringWithPrefix(prefixAloneOrPrefixedMention, expectedRange: expectedRange)
else {
return nil
}
self.keyword = text.substringWithRange(nsrange)
self.nsrange = nsrange
} }
As can be seen start is always bigger than end. That's why in expectedRange length(which is end-start) can never be minus.
And finally my code which causes the crash is below(The line starting with "tweetTextLabel?.attributedText" causes the crash)
class TweetTableViewCell: UITableViewCell
{
#IBOutlet weak var tweetScreenNameLabel: UILabel!
#IBOutlet weak var tweetTextLabel: UILabel!
#IBOutlet weak var tweetProfileImageView: UIImageView!
#IBOutlet weak var tweetCreatedLabel: UILabel!
var tweet: Twitter.Tweet? {
didSet {
updateUI()
}
}
var mentionsList = [Array<Mention>]()
private func updateUI()
{
// reset any existing tweet information
tweetTextLabel?.attributedText = nil
tweetScreenNameLabel?.text = nil
tweetProfileImageView?.image = nil
tweetCreatedLabel?.text = nil
// load new information from our tweet (if any)
if let tweet = self.tweet
{
tweetTextLabel?.text = tweet.text
if tweetTextLabel?.text != nil {
for _ in tweet.media {
tweetTextLabel.text! += " 📷"
}
}
mentionsList.append(tweet.hashtags)
mentionsList.append(tweet.urls)
mentionsList.append(tweet.userMentions)
var mentionColor = UIColor.clearColor()
for mentions in mentionsList {
switch mentions {
case _ where mentions == tweet.hashtags:
mentionColor = UIColor.blueColor()
case _ where mentions == tweet.urls:
mentionColor = UIColor.redColor()
case _ where mentions == tweet.userMentions:
mentionColor = UIColor.brownColor()
default: break
}
for mention in mentions {
(tweetTextLabel?.attributedText as? NSMutableAttributedString)?.addAttribute(
NSForegroundColorAttributeName, value: mentionColor, range: mention.nsrange) // Causes the crash
}
}
tweetScreenNameLabel?.text = "\(tweet.user)" // tweet.user.description
if let profileImageURL = tweet.user.profileImageURL {
if let imageData = NSData(contentsOfURL: profileImageURL) { // blocks main thread!
tweetProfileImageView?.image = UIImage(data: imageData)
}
}
let formatter = NSDateFormatter()
if NSDate().timeIntervalSinceDate(tweet.created) > 24*60*60 {
formatter.dateStyle = NSDateFormatterStyle.ShortStyle
} else {
formatter.timeStyle = NSDateFormatterStyle.ShortStyle
}
tweetCreatedLabel?.text = formatter.stringFromDate(tweet.created)
}
}
}
Please help me. I am already working on this issue for several days and cannot find where the mistake is. Maybe because of some special characters the number of characters in string are more. But I don't know how to figure it out.
I have two classes in Swift, one is a ViewController.swift, another has some business logic, called Brain.swift. In Brain.swift I have a class which contains a function called convert() which executes an NSTask.
In ViewController.swift all of the UI updating occurs.
What I would like to accomplish is getting the output of the convert()'s NSTask into a TextView in the ViewController.
I have implemented the solution from this answer, but I'm a bit of a novice so I'm unsure how to return it as a class property in real time to be accessible by other classes.
Brain.swift
import Foundation
internal func convert(chosenFile: NSURL, addText: (newText: String) -> Void) {
let bundle = NSBundle.mainBundle()
let task = NSTask()
let outputPipe = NSPipe()
task.standardOutput = outputPipe
let outHandle = outputPipe.fileHandleForReading
outHandle.readabilityHandler = { outputPipe in
if let line = String(data: outputPipe.availableData, encoding: NSUTF8StringEncoding) {
addText(newText: line)
} else {
print("Error decoding data: \(outputPipe.availableData)")
}
}
task.launch()
task.waitUntilExit()
}
ViewController.swift
#IBAction func Run(sender: AnyObject) {
let qualityOfServiceClass = QOS_CLASS_USER_INITIATED
let userInitiatedQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(userInitiatedQueue, {
self.btnConvert.enabled = false
self.btnSelect.enabled = false
self.activitySpinner.hidden = false
self.activitySpinner.startAnimation(self)
convert(self.inputFile.chosenFile) { newText in
self.statusText.stringValue = "\(newText)"
}
})
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.statusText.stringValue = "Done!"
self.activitySpinner.hidden = true
self.activitySpinner.stopAnimation(self)
self.btnSelect.enabled = true
})
}
This should work:
func convert(chosenFile: NSURL, addText: (newText: String) -> Void) {
let task = NSTask()
// Set up task
let pipe = NSPipe()
task.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.readabilityHandler = { pipe in
if let line = String(data: pipe.availableData, encoding: NSUTF8StringEncoding) {
addText(newText: line)
} else {
print("Error decoding data: \(pipe.availableData)")
}
}
task.launch()
}
You can call it like this:
convert(myURL) { newText in
print("New output available: \(newText)")
}
I made a function out of your class because there's no reason for a class when you're just gonna use it for a single function. The readabilityHandler approach also simplifies your code greatly.
I also added this way of getting updates to the mentioned question.