UILabel.text is not updated inside the main thread. labelOne is updated but labelTwo which is to show translated word is not updated. When I print translatedWord it prints right string to console but UILabel is not updated.
datatask = session.dataTask(with: request, completionHandler: {data, response, error in
if error == nil {
let receivedData = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]
DispatchQueue.main.async {
self.labelOne.text = wordTobeTranslated
let data = "\(String(describing: receivedData!["text"]))"
let all = data.components(separatedBy: "(")
let afterAll = all[2]
let last = afterAll.components(separatedBy:")" )
self.translatedWord = last[0]
self.importantWords.append(last[0])
self.labelTwo.text = self.translatedWord
print(self.translatedWord)
}
}
})
datatask?.resume()
Some tips to debug your issue:
Try to set some hard coded string to labelTwo and check whether it
is displaying. (or)
Update any value for labelTwo in Storyboard and check whether is is
displaying.
If string is displayed from any of the above steps, then labelTwo is configured correctly in storyboard.
You can also try self.labelTwo.layoutIfNeeded() method after updating the text to force update the UI.
If none of the steps helps you, check the Font color of UILabel. If it is same as background color, it would not be seen.
Looking at your code sample, it would appear that you’re trying to retrieve:
describing: receivedData!["text"]
from:
\(String(describing: receivedData!["text"]))
The problem is that the \(...) in a String literal results in string interpolation where the expression inside \( and ) will be evaluated and that’s what will be place in the string. And the String(describing: ...) will interpret the value and return a string representation. So, let’s say that receivedData!["text"] contained the word “Foo”. Then
let data = "\(String(describing: receivedData!["text"]))"
Would result in data containing the string, Optional("Foo").
If you want to remove that Optional(...) part, you should either unwrap the optional or use a nil-coalescing operator, ??. And frankly, rather than using string interpolation at all, I’d just do:
let data = String(describing: receivedData?["text"] ?? "")
I know this is an old question, but for new searchers: Try setting number of lines of UILabel to Zero. It might be a constraints issue.
Related
I am trying to add empty space in the string but after adding it spaces are not showing and If I check the length of String it shows different spaces are there but spaces are not there in display
below is my code and screenshot of the same.
let apple = "apple"
var arabictext = "بيتزا صغيرة بالجبنة"
// here space works
let ababictextWithSpace = "\(apple) \(arabictext)"
print(ababictextWithSpace)
// here space not work
var ababictextWithSpace2 = " \(arabictext) "
print(arabictext)
let str = String(data: arabictext.data(using: .utf8)!, encoding: .utf8)
print("\(str!)".count)
print(" \(str!)".count)
Screenshot with output.
you can try wrapping your string with (""")
Maybe it's console's bug.
In fact they're different, the thing that's different size just proof that.
I'm creating a reading list app, and I'd like to pass the read time of a user added link to a table cell in their reading list - and the only way to get that number is from that page's word count. I've found a few solutions, namely Parsehub, Parse and Mercury but they seem to be geared more towards use cases that need more advanced things to be scraped from a url. Is there a simpler way in Swift to calculate word count of a url?
First of all, you need to parse the HTML. HTML can only be parsed reliably with dedicated HTML parser. Please don't use Regular Expressions or any other search method to parse HTML. You may read it why from this link. If you are using swift, you may try Fuzi or Kanna. After you get the body text with any one of the library, you have to remove extra white spaces and count the words. I have written some basic code with Fuzi library for you to get started.
import Fuzi
// Trim
func trim(src:String) -> String {
return src.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
// Remove Extra double spaces and new lines
func clean(src:String) ->String {
return src.replacingOccurrences(
of: "\\s+",
with: " ",
options: .regularExpression)
}
let htmlUrl = URL(fileURLWithPath: ((#file as NSString).deletingLastPathComponent as NSString).appendingPathComponent("test.html"))
do {
let data = try Data(contentsOf: htmlUrl)
let document = try HTMLDocument(data: data)
// get body of text
if let body = document.xpath("//body").first?.stringValue {
let cleanBody = clean(src: body)
let trimmedBody = trim(src:cleanBody)
print(trimmedBody.components(separatedBy: " ").count)
}
} catch {
print(error)
}
If you are fancy, you may change my global functions to String extension or you can combine them in a single function. I wrote it for clarity.
We're adding the finishing touches to an app we're working on, and that apparently means putting an entire "Terms and Conditions" and "FAQs" section, formatting, bullets, breaks and all.
So I tried copy-pasting it into a textView with "editable" set to off, which kept the bullets, but not the bolded text.
Now, I've done attributed string before, and I have to say, I'm not sure it will be easy to do that on some 12-pages worth of paragraphs, bulleted lists and breaks that are likely to change in a few years or so.
So my question is, is there a way to do this without using attributed string?
Barring that, perhaps there's a way to loop through the text, and look for a written tag that will apply the attributes?
EDIT:
Update. It's been suggested I use HTML tags, and web view. That's what was done for the FAQs (which uses a label), I neglected to mention I tried that too.
For some reason, it just shows a blank textview, albeit a large-sized one, as if there's text in it (there isn't any). Strange that copy-pasting works but this doesn't.
Here's my code for it:
override func awakeFromNib() {
super.awakeFromNib()
termsTitle.text = "Terms and Conditions"
htmlContent = "<p style=\"font-family:Helvetica Neue\"><br/><strong><br/> BLA BLA BLA BLA BLA BLA x 12 Pages"
do {
let str = try NSAttributedString(data: htmlContent.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!, options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
termsTextView.attributedText = str
} catch {
print("Dim background error")
}
}
I'm pretty sure you can't do this in a Textview without using AttributedString. A possible solution would be using a WebView. Converting your "Terms and Conditions" and "FAQ" to HTML would probably be much easier than using an AttributedString.
If you still want to use your HTML in a UITextView you can try this function:
func getAttributedString(fileName: String) -> NSAttributedString? {
if let htmlLocation = NSBundle.mainBundle().URLForResource(fileName, withExtension: "html"), data = NSData(contentsOfURL: htmlLocation) {
do {
let attrString = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType], documentAttributes: nil)
return attrString
} catch let err as NSError {
print("Attributed String Creation Error")
print(err.localizedDescription)
return nil
}
} else {
return nil
}
}
This function assumes you have a .html file in your main bundle. You pass it the name (minus extension) of the file (that should be in your project) and then use it like so:
textView.attributedText = getAttributedString("TermsAndConditions")
Just to clarify, the textView is a #IBOutlet on a View Controller in this example.
This function returns nil if either the .html file does not exist or the NSAttributedString conversion failed.
So I am using the CZWeatherKit library to grab weather data from forecast.io.
When I get results, it sends a climacon UInt8 char, which should match to an icon if the climacon font is installed. I did that but it only shows the char, not the actual icon. Here is the code, it prints a quote i.e. " which is the correct mapping to ClimaconCloudSun, but the icon doesn't show. I followed these instructions to install the climacons.ttf font
request.sendWithCompletion { (data, error) -> Void in
if let error = error {
print(error)
} else if let weather = data {
let forecast = weather.dailyForecasts.first as! CZWeatherForecastCondition
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// I get back good results, this part works
let avgTempFloat = (forecast.highTemperature.f + forecast.lowTemperature.f) / 2
let avgTemp = NSDecimalNumber(float: avgTempFloat).decimalNumberByRoundingAccordingToBehavior(rounder)
self.temperatureLabel.text = String(avgTemp)
self.weatherLabel.text = forecast.summary
// this part does not work, it has the right char, but does not display icon
// I tried setting self.climaconLabel.font = UIFont(name: "Climacons-Font", size: 30) both in IB and programmatically
let climaChar = forecast.climacon.rawValue
let climaString = NSString(format: "%c", climaChar)
self.climaconLabel.text = String(climaString)
})
}
}
I solved the exact same issue, the problem was the font file. Replace your current font with the one provided here: https://github.com/comyar/Sol/blob/master/Sol/Sol/Resources/Fonts/Climacons.ttf
You've probably moved on from this problem by now, but I'll leave this here for future use.
You need to call setNeedsLayout on the label after you change the title text to the desired value, and the label will change to the corresponding icon.
I am using some Facebook IDs in my app, and I have an array of serveral ID's, the array can be 10 numbers but can also be 500 numbers..
Right now the numbers are displayed in a tableview, and I want all the results there too, so they need to be in an array.
let profileUrl = NSURL(string:"http://www.facebook.com/" + newArray[0])!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
// Will happen when task completes
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
dispatch_async(dispatch_get_main_queue(),
{ () -> Void in
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
//print(websiteArray[1])
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
print(secondArray[0])
})
}
}
this code takes the first number of the array, goes to facebook.com/[the actual number], and then downloads the data and splits the data into pieces, so that the data that I want it in the secondArray[0]. I want to do this for every number of the array, take the result data and put it back into an array. I have no idea how to do this because you don't know how much numbers there are gonna be etc, does someone has a good solution for this?
Any help would be appreciated, really!
Thanks
You have several problems here, and you should take them one at at a time to build up to your solution.
First, forget the table for the moment. Don't worry at all about how you're going to display these results. Just focus on getting the results in a simple form, and then you'll go back and convert that simple form into something easy to display, and then you'll display it.
So first, we want this in a simple form. That's a little bit complicated because it's all asynchronous. But that's not too hard to fix.
func fetchTitle(identifier: String, completion: (title: String) -> Void) {
let profileUrl = NSURL(string:"http://www.facebook.com/" + identifier)!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
let title = secondArray[0]
completion(title: title)
}
}
task.resume()
}
Now this is still pretty bad code because it doesn't handle errors at all, but it's a starting point, and the most important parts are here. A function that takes a string, and when it's done fetching things, calls some completion handler.
(Regarding error handling, note how many places this code would crash if it were returned surprising data. Maybe the data you get isn't a proper string. Maybe it's not formatted like you think it is. Every time you use ! or subscript an array, you run the risk of crashing. Try to minimize those.)
So you might then wrap it up in something like:
var titles = [String]()
let identifiers = ["1","2","3"]
let queue = dispatch_queue_create("titles", DISPATCH_QUEUE_SERIAL)
dispatch_apply(identifiers.count, queue) { index in
let identifier = identifiers[index]
fetchTitle(identifier) { title in
dispatch_async(queue) {
titles.append(title)
}
}
}
This is just code to get you on the right track and start studying the right things. It certainly would need work to be production quality (particularly to handle errors).
Once you have something that returns your titles correctly, you should be able to write a program that does nothing but take a list of identifiers and prints out the list of titles. Then you can add code to integrate that list into your tableview. Keep the parts separate. The titles are the Model. The table is the View. Read up on the Model-View-Controller paradigm, and you'll be in good shape.
To repeat code for whole array put your code in a loop and run that loop from 0 to array.count-1
You don't need to know how many items there will be an array. You can just get the count at run time array.count here array is your array.
I hope this is what you wanted to know, your question doesn't make much sense though.