How to Get the Title of a HTML Page Displayed in UIWebView? - ios

I need to extract the contents of the title tag from an HTML page displayed in a UIWebView. What is the most robust means of doing so?
I know I can do:
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSString *theTitle=[webView stringByEvaluatingJavaScriptFromString:#"document.title"];
}
However, that only works if javascript is enabled.
Alternatively, I could just scan the text of the HTML code for the title but that feels a bit cumbersome and might prove fragile if the page's authors got freaky with their code. If it comes to that, what's the best method to use for processing the html text within the iPhone API?
I feel that I've forgotten something obvious. Is there a better method than these two choices?
Update:
Following from the answer to this question: UIWebView: Can You Disable Javascript? there appears to be no way to turn off Javascript in UIWebView. Therefore the Javascript method above will always work.

For those who just scroll down to find the answer:
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSString *theTitle=[webView stringByEvaluatingJavaScriptFromString:#"document.title"];
}
This will always work as there is no way to turn off Javascript in UIWebView.

WKWebView has 'title' property, just do it like this,
func webView(_ wv: WKWebView, didFinish navigation: WKNavigation!) {
title = wv.title
}
I don't think UIWebView is suitable right now.

If Javascript Enabled Use this :-
NSString *theTitle=[webViewstringByEvaluatingJavaScriptFromString:#"document.title"];
If Javascript Disabled Use this :-
NSString * htmlCode = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.appcoda.com"] encoding:NSASCIIStringEncoding error:nil];
NSString * start = #"<title>";
NSRange range1 = [htmlCode rangeOfString:start];
NSString * end = #"</title>";
NSRange range2 = [htmlCode rangeOfString:end];
NSString * subString = [htmlCode substringWithRange:NSMakeRange(range1.location + 7, range2.location - range1.location - 7)];
NSLog(#"substring is %#",subString);
I Used +7 and -7 in NSMakeRange to eliminate the length of <title> i.e 7

Edit: just saw you found out the answer... sheeeiiitttt
I literally just learned this! To do this, you don't even need to have it displayed in UIWebView. (But as you are using it, you can just get the URL of the current page)
Anyways, here's the code and some (feeble) explanation:
//create a URL which for the site you want to get the info from.. just replace google with whatever you want
NSURL *currentURL = [NSURL URLWithString:#"http://www.google.com"];
//for any exceptions/errors
NSError *error;
//converts the url html to a string
NSString *htmlCode = [NSString stringWithContentsOfURL:currentURL encoding:NSASCIIStringEncoding error:&error];
So we have the HTML code, now how do we get the title? Well, in every html-based doc the title is signaled by This Is the Title
So probably the easiest thing to do is to search that htmlCode string for , and for , and substring it so we get the stuff in between.
//so let's create two strings that are our starting and ending signs
NSString *startPoint = #"<title>";
NSString *endPoint = #"</title>";
//now in substringing in obj-c they're mostly based off of ranges, so we need to make some ranges
NSRange startRange = [htmlCode rangeOfString:startPoint];
NSRange endRange = [htmlCode rangeOfString:endPoint];
//so what this is doing is it is finding the location in the html code and turning it
//into two ints: the location and the length of the string
//once we have this, we can do the substringing!
//so just for easiness, let's make another string to have the title in
NSString *docTitle = [htmlString substringWithRange:NSMakeRange(startRange.location + startRange.length, endRange.location)];
NSLog(#"%#", docTitle);
//just to print it out and see it's right
And that's really it!
So basically to explain all the shenanigans going on in the docTitle, if we made a range just by saying NSMakeRange(startRange.location, endRange.location) we would get the title AND the text of startString (which is ) because the location is by the first character of the string.
So in order to offset that, we just added the length of the string
Now keep in mind this code is not tested.. if there are any problems it might be a spelling error, or that I didn't/did add a pointer when i wasn't supposed to.
If the title is a little weird and not completely right, try messing around with the NSMakeRange-- I mean like add/subtract different lengths/locations of the strings --- anything that seems logical.
If you have any questions or there are any problems, feel free to ask. This my first answer on this website so sorry if it's a little disorganized

Here is Swift 4 version, based on answer at here
func webViewDidFinishLoad(_ webView: UIWebView) {
let theTitle = webView.stringByEvaluatingJavaScript(from: "document.title")
}

I dońt have experience with webviews so far but, i believe it sets it´s title to the page title, so, a trick I suggest is to use a category on webview and overwrite the setter for self.title so you add a message to one of you object or modify some property to get the title.
Could you try and tell me if it works?

If you need it frequently in your code, i suggest you to add a func into "extension UIWebView" like this
extension UIWebView {
func title() -> String {
let title: String = self.stringByEvaluatingJavaScript(from: "document.title")!
return title
}
}
alternatively its better to use WKWebView.
Unfortunately, its not well supported in ARKit. I had to give up on WKWebView. I couldnt load the website into the webView. If someone has a solution to this issue here -> i have a simlar problem, it would help greatly.

Related

Saving large text file with sections to Core Data attributes

I want to save this .txt file to core data
http://openweathermap.org/help/city_list.txt
But as you can see it has 5 different sections
1)id (city ID)
2)nm (name)
3)lat (latitude)
4)lon (longitude)
5)countryCode
I'd like to download the file & save each section into a core data attribute.
I've looked around and could not find any info on how to do this. I'm a beginner to core data and databases, so sorry if this is a very newbie question.
Thanks and let me know if theres any other info I can provide
The first thing you have to do here is download the data. You can do that by using NSURLConnection. Then you'd like to read it line by line, to get the different cities. When you're reading it line by line, you can get each individual field by separating them by the tab-character (\t). When you have each individual field, remember to put them in the database.
Example code:
- (void)downloadAndParseCityList {
NSURL *listURL = [NSURL URLWithString:#"http://openweathermap.org/help/city_list.txt"];
NSURLRequest *request = [NSURLRequest requestWithURL:listURL]; //Forge the request to be used by NSURLConnection.
NSData *cityData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; //You probably use an asynchrounous request instead, but I'm too lazy to do that here.
NSString *cityString = [[NSString alloc] initWithData:cityData encoding:NSASCIIStringEncoding]; //We're going to work with the data as an NSString.
NSArray *cities = [cityString componentsSeparatedByString:#"\n"]; //By getting the components seperated by line-break, it is easier to work with each individual city more like an object.
for(NSUInteger i = 1; i <= [cities count]-1; i++) { //We start at i=1 because we don't want to parse the first line in the file ("id nm lat lon countryCode"), as these are just the field names.
NSString *cityString = cities[i];
NSArray *cityFields = [cityString componentsSeparatedByString:#"\t"]; //All the fields are seperated by a tab ('\t'), that makes it easy to read all the fields.
for(NSString *field in cityFields) {
//Here you probably want to do something with the fields. Save them to a Core Data database or something.
}
}
}
You'll have to figure out the part about Core Data for yourself, as I have not used that enough to be comfortable with posting anything about it as an answer.
(The code has not been tested, so it might not work out of the box.)
Edit: I'm sorry, code doesn't work at all, seems the data was too big for an NSString, but first part of the answer still applies. First parse each individual city by line-break (\n) and then parse each field by tab (\t).
EDIT 2:
Code now works perfectly after changing the encoding to NSASCIIStringEncoding

Keyboard extension no common words, no autocorrect

I've been working on a little keyboard extension, and now I've reached the fase of implementing autocorrect, or something that will show what the user could be attempting to write.
The keyboard is just for myself, so just danish.
The documentation says that UILexicon contains some common words, which it doesn't. It contains my contacts, and not even that works. The only thing I get is like this: morten=morten, where it should be morten=Morten. They're all like this.
Then I tried the UITextChecker, and tried this in a playground (Swift, but the keyboard is in ObjectiveC:
import UIKit
var str:String = "h"
var textChecker:UITextChecker = UITextChecker();
var range:NSRange = NSMakeRange(0, 1)
var lan:NSArray = UITextChecker.availableLanguages()
var t:String = lan[1] as String
var ar:NSArray = textChecker.completionsForPartialWordRange(range, inString: str, language: t)!
This returns results in any language but da_DK, which is somewhat weird.
How should I do this? Should I download a danish dictionary and use it in the app using some kind of mysql database?
Is my code somehow wrong, or is UILexicon simply broken for the danish language?
Try taking a look at the UITextChecker class.
It's pretty strait forward to use:
NSString * test = #"stavefej";
NSArray * suggestions = [self.spellChecker guessesForWordRange:NSMakeRange(0, [test length]) inString:test language:#"da_DK"];
NSArray * autoCom = [self.spellChecker completionsForPartialWordRange:NSMakeRange(0, [test length]) inString:test language:#"da_DK"];

How to fix string in objective-c?

I'm having a problem with the correct answer
When the user types in lets say "dog" the answer is right!
But if s/he types in "dog " <--- with a spacebar its wrong,
how do i fix this:
Code:
- (IBAction)btncheck:(id)sender {
if ([_textbox.text isEqualToString:#"q"]) {
_keyboard.hidden = YES;
_textXclear.hidden = YES;
}
else {
// Was not correct. Notify user, or just don't do anything
}
and id like to notify user that the answer was not correct by placing an image, how is that done
You could use the method stringByTrimmingCharactersInSet to get rid of leading or trailing spaces:
NSString *string = [_textbox.text stringByTrimmingCharactersInSet: whitespaceCharacterSet];
if (string isEqualToString: "q")
{
//string is wrong? If so, display an error message.
}
else
{
//string is correct, resign first responder
}
You should do a search on NSString in the Xcode help system and read the NSString class reference. There are tons of useful methods in the NSString class for doing things like this.
I'm confused, because in your previous post I thought that the answer "q" was the correct answer. In your code above, anything but q would be correct.
As far as placing an image, the easiest thing to do is probably to put an image view, with image installed, in your view controller, but set it's hidden property to YES. Then, when you decide the user has entered the correct answer, set the image view's hidden property to NO to reveal it.

Is this the standard way to get text from label and concatenate with string in iOS?

Please bear with me as I'm very new to the world of iOS and Objective-C. I've read Apple's Obj-C primer, as well as a few free ones provided on the web.
On a button press, I'm trying to simply take the text of a label and concatenate it with a string. My mindset is still very much in Android/Java and how simple it could be, but I'm having trouble here. Nonetheless here is my code:
- (IBAction)myButton:(UIButton *)sender {
self.myLabel.text = [self.myLabel.text stringByAppendingString:#"obj-c is hard =/"];
}
It seems pretty standard, but I can imagine myself doing this often so I want to make sure this is correct or what other ways are there to do this?
Yes this is correct way. And if you want to use another method then use this one
self.myLabel.text = [NSString stringWithFormat:#"%# obj-c is hard =/",self.myLabel.text];
It is the standard way to join string.As ios updated syntaxes to make it easy like NSArray and NSDictiornary delaration but for concatenation it has not declared any shortcut way.
Have a look at this
OR
you can use a trick to simplify concatenation of string.Pass a parameter to macro and use following joining literal syntax.
#define STRING(text) #""text""
#implementation SPTViewController
- (void)viewDidLoad
{
NSString *joinedFromLiterals =STRING(#"Congratulations!, ") #"I " #"really " #"enjoy " #"carpenting!";
NSLog(#"joined string %#",joinedFromLiterals);
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
output is ---------
joined string Congratulations!, I really enjoy carpenting!
Yes, this is correct, but there is a gotcha. If you haven't previously set the value of self.myLabel.text, it will be nil by default. Then the result of calling any method (like [self.myLabel.text stringByAppendingString:#"obj-c is hard =/"]) will also be nil, so myLabel will still have empty text. The way Objective-C handlesnil values is different than handling null in Java.
So to be safe, initialize label's text first:
self.myLabel.text = #"";
You are doing it right. Sure Objective-C is a bit more verbose than C# or Java or even Visual Basic .net (as I used to work on all those languages) but don't be bugged by those long method names. Although some #defines can be very helpful like (rewritten as C inline function):
static inline __attribute__((always_inline))
__attribute__((format(NSStirng, 1, 2)) NSString *SKSTR(NSString *fmt, ...)
{
va_list args;
va_start(args, fmt);
NSString *string = [[NSString alloc] initWithFormat:fmt arguments:args];
va_end(args);
#if !__has_feature(objc_arc)
[string autorelease];
#endif
return string;
}
Hope the __attribute__s and #ifs does not confuse you - you can safely ignore them.
To use:
self.label.text = SKSTR(#"%#, ugh!", self.label.text); // just like NSLog or snprintf :)

Using One or More Formatters with a Page Renderer in iOS

Has anyone tried using multiple formatters (UIViewPrintFormatter, UIMarkupTextPrintFormatter, UISimpleTextPrintFormatter) with a page renderer (UIPrintPageRenderer) to print the content?
I'm trying to use two UIMarkupTextPrintFormatters with a UIPrintPageRenderer subclass, but i'm failing to get the print. I'm using MyPrintPageRenderer class from PrintWebView sample code.
I've gone through the Apple's documentation but it isn't very helpful, and there is no sample code associated with the description. I've tried a couple of solutions but so far I haven't had any success.
Any suggestions?
The fact that there is very little activity in the "Printing" section suggests that either not many people are using this, or people using this API are not visiting this community, or people are just ignoring the question for some reason.
Anyways, i was able to solve my problem. I was using setPrintFormatters: method earlier which wasn't/isn't working. I don't know why. So, i started experimenting with addPrintFormatter:startingAtPageAtIndex: method instead. And here's how i solved my problem:
// To draw the content of each page, a UIMarkupTextPrintFormatter is used.
NSString *htmlString = [self prepareWebViewHTML];
UIMarkupTextPrintFormatter *htmlFormatter = [[UIMarkupTextPrintFormatter alloc] initWithMarkupText:htmlString];
NSString *listHtmlString = [self prepareWebViewListHTMLWithCSS];
UIMarkupTextPrintFormatter *listHtmlFormatter = [[UIMarkupTextPrintFormatter alloc] initWithMarkupText:listHtmlString];
// I think this should work, but it doesn't! The result is an empty page with just the header and footer text.
// [myRenderer setPrintFormatters:[NSArray arrayWithObjects:htmlFormatter, listHtmlFormatter, nil]];
// Alternatively, i've used addPrintFormatters here, and they just work!
// Note: See MyPrintPageRenderer's numberOfPages method implementation for relavent details.
// The important point to note there is that the startPage property is updated/corrected.
[myRenderer addPrintFormatter:htmlFormatter startingAtPageAtIndex:0];
[myRenderer addPrintFormatter:listHtmlFormatter startingAtPageAtIndex:1];
In the MyPrintPageRenderer, i used following code to update/correct the startPage property so that a new page is used for each formatter:
- (NSInteger)numberOfPages
{
// TODO: Perform header footer calculations
// . . .
NSUInteger startPage = 0;
for (id f in self.printFormatters) {
UIPrintFormatter *myFormatter = (UIPrintFormatter *)f;
// Top inset is only used if we want a different inset for the first page and we don't.
// The bottom inset is never used by a viewFormatter.
myFormatter.contentInsets = UIEdgeInsetsMake(0, leftInset, 0, rightInset);
// Just to be sure, never allow the content to go past our minimum margins for the content area.
myFormatter.maximumContentWidth = self.paperRect.size.width - 2*MIN_MARGIN;
myFormatter.maximumContentHeight = self.paperRect.size.height - 2*MIN_MARGIN;
myFormatter.startPage = startPage;
startPage = myFormatter.startPage + myFormatter.pageCount;
}
// Let the superclass calculate the total number of pages
return [super numberOfPages];
}
I still don't know if there is anyway to APPEND the printable content of both htmlFormatter and listHtmlFormatter (using this approach). e.g. rather than using a new page for listHtmlFormatter, continue printing from where htmlFormatter ended.
I'm adding this extra info answer here because I spent 2 days dk'n around with this and Mustafa's answer saved me. Hopefully all this in one place will save others a lot of wasted time.
I was trying to figure out why I was unable get my custom UIPrintPageRenderer to iterate over an array of viewPrintFormatter from several UIWebViews that I need to print in ONE job, and have it properly calculate pageCount as these Apple docs suggest it should: PrintWebView
The key was to add them via this method:
-(void)addPrintFormatter:(UIPrintFormatter *)formatter startingAtPageAtIndex:(NSInteger)pageIndex
and NOT this one:
#property(nonatomic, copy) NSArray <UIPrintFormatter *> *printFormatters
The Apple docs suggest that the UIPrintPageRenderer gets page count from the print formatters in the array as long as the proper metrics are set on the print formatters, as Mustafa shows above. But it only works if you add them via addPrintFormatter:startingAtPageAtIndex:
If you add them as an array, the printFormatters (regardless of what metrics you set on them) will return a page count of 0!
One additional note for people using this approach, put the update to viewPrintFormatter.startPage in a dispatch_async block, else you'll get an exception:
dispatch_async(dispatch_get_main_queue(), ^{
myFormatter.startPage = startPage;
startPage = myFormatter.startPage + myFormatter.pageCount;
});
To add to #Mustafa's answer,
startPage needs to be declared as:
__block NSUInteger startPage
if it's to be used inside the dispatch_async block

Resources