I have an iPad application that uses WebViews to display a list of URL's. I would like to be able to print all of the WebViews in one go, without prompting the user multiple times with the PrintInteractionController. The problem is that the PrintInteractionController does not appear to have the ability to do this. You cannot assign multiple viewFormatters, and the WebViews are not recognized as printItems. There is also no method that I can find to just print the items and not show the PrintInteractionController.
Does anyone know of a way to do this?
Cheers.
This is an old question, but I spent several days searching for an answer and following some misleading comments in the Apple docs and sample code. So, I'm sticking this here to save the next guy the days I wasted.
The specific problem is: How does one write a UIPrintPageRenderer that can print multiple UIWebViews in a single print job given to a UIPrintInteractionController?
You can get pretty far along to a solution that doesn't involve converting everything to PDFs first using this Apple sample code: PrintWebView. Also this documentation will help some: UIPrintPageRenderer. The problem is that the sample App and the documentation for UPPrintPageRenderer suggest that if you add multiple UIPrintFormatter via this property:
#property(nonatomic,copy) NSArray <UIPrintFormatter *> *printFormatters
The sample code Apple provided for the method to override -(NSInteger)numberOfPages in PrintWebView will just work and figure out the correct page counts.... Nope!
What you have to do is add the printFormatters in a non-obvious way via the method shown below, and then correct their respective startPage properties and use these collectively to work out the correct pageCount for UIPrintPageRenderer to return. Instead, use this:
-(void)addPrintFormatter:(UIPrintFormatter *)formatter startingAtPageAtIndex:(NSInteger)pageIndex
Why does this works and the other doesn't? No idea, probably a bug.... I should file a radar :) But Thanks to #Mustafa and this other Stackoverflow answer for the hint:
Here are the steps to follow to print multiple UIWebViews in one print job:
Follow the Apple outline in the PrintWebView example referenced above and write your UIPrintPageRenderer to set the properties you want on the webView.viewPrintFormatters you give to it.
Add the printFormatters via: [addPrintFormatter:startingAtPageAtIndex:]
In your UIPrintPageRenderer -(NSInteger)numberOfPages method get the pageCount from each formatter, and use that to update the startPage and total page count values.
Here is the basic code outline to follow (FYI: this solution is working for an App built with a deployment target of iOS 9.0):
Define your UIPrintPageRenderer class like so (you don't really need the init method, but in my use case I set values in there I always want that way):
#interface MyPrintPageRenderer : UIPrintPageRenderer
#end
#implementation MyPrintPageRenderer
-(id)init
{
self = [super init];
if(self) {
self.headerHeight = 0;
self.footerHeight = 0;
}
return self;
}
//
// Set whatever header, footer and insets you want. It is
// important to set these values to something, so that the print
// formatters can figure out their own pageCounts for whatever
// they contain. Look at the Apple sample App for PrintWebView for
// for more details about these values.
//
-(NSInteger)numberOfPages
{
__block NSUInteger startPage = 0;
for(UIPrintFormatter *pf in self.printFormatters) {
pf.contentInsets = UIEdgeInsetsZero;
pf.maximumContentWidth = self.printableRect.size.width;
pf.maximumContentHeight = self.printableRect.size.height;
dispatch_async(dispatch_get_main_queue(), ^{
pf.startPage = startPage;
startPage = pf.startPage + pf.pageCount;
});
}
return [super numberOfPages];
}
#end
In the Controller that handles the print action for the UIWebViews you want to print, do something like this:
-(void)printWebViews
{
MyPrintPageRenderer *pr = [MyPrintPageRenderer new];
//
// Add the printFormatters at sequential startingPageIndexes,
// your renderer will set them to the correct values later based
// on the various page metrics and their content.
//
[pr addPrintFormatter:_webView1.viewPrintFormatter startingAtPageAtIndex:0];
[pr addPrintFormatter:_webView2.viewPrintFormatter startingAtPageAtIndex:1];
[pr addPrintFormatter:_webView3.viewPrintFormatter startingAtPageAtIndex:2];
// Set whatever values needed for these as per Apple docs
UIPrintInfo *pi = [UIPrintInfo printInfo];
pi.outputType = UIPrintInfoOutputGeneral;
UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
pic.printInfo = pi;
pic.printPageRenderer = pr;
[pic presentAnimated:NO completionHandler:nil];
}
And voila! Now... it just works ;)
Use printPageRenderer property of your UIPrintIterationController object. You can set multiple UIPrintFormatter subclasses in a UIPrintPageRenderer subclass object.
Related
So I have 3 objects in my project right now:
_vLabel.hidden = YES;
_wLabel.hidden = YES;
_nextButton.hidden = YES;
As you can see I do the .hidden thing for all of them . Is there a way of creating variable or something with which I would only have to apply it instead writing the whole line, like:
_vLabel.hide;
Thanks in advance!
It seems silly to do it, as you should just follow the convention that any other iOS programmer expects and understands, but yes you can do it:
UIView+MyCategory.h:
#interface UIView (MyCategory)
-(void) hide;
#end
UIView+MyCategory.m:
#implementation UIView (MyCategory)
-(void) hide {
[self setHidden:YES];
}
#end
usage:
[myView hide];
I find the question unclear. What would be a "shortcut" in this situation? How can you get any "shorter" than this:
_vLabel.hidden = YES;
_wLabel.hidden = YES;
_nextButton.hidden = YES;
To me, the "length" here - the thing that needs shortening - is that you're doing something three times instead of once. If these objects are objects that you typically hide and show together, then it would be nice to have one method that hides and shows all of them:
- (void) toggle {
_vLabel.hidden = !_vLabel.hidden;
_wLabel.hidden = !_wLabel.hidden;
_nextButton.hidden = !_nextButton.hidden;
}
It's just as much code, but once you've written it, then every time you want to show them all or hide them all you just say:
self.toggle;
Which is itself actually a shortcut for:
[self toggle];
So what you'd be doing here is writing a method in order to change what the language a little, and that's a perfectly reasonable thing to do if it avoids repetition.
Hmm, if your only problem is that writing label.hidden = YES; is too long then why donĀ“t you just use code snippets? That way you keep using the naming conventions with less writing.
You can even use Xcode to create these snippets. Just select the text and drag it from the edge(this part can be pretty tricky) to the snippets area.
Personally though I would use something like TextExpander or aText or whatever else you prefer to do this. These apps are usually faster than Xcode and easier to use.
Just create in those apps snippet for .hidden = YES; and assign it to a shortcut like .hide
So now whenever you want to hide something you just write myLabel.hide TextExpander will expand it into
myLabel.hidden = YES;
And to make things visible again you could assign .show to .hidden = NO;
I am trying to implement a feature in iOS project that when you select a piece of text and highlight it you can then choose from the menu options to use another app like the default dictionary. Is it possible to do this? If so where can I find such documentation or tutorials?
You are describing the iOS menu. Look at the documentation on classes such as UIMenu, UIMenuItem, and UIMenuController.
I've found a solution to my problem.
Thanks to the author of this article:
http://blog.studiovillegas.com/2014/02/06/ios-uipasteboard-uimenucontroller-and-uimenuitem/
To add a custom menu item on to the default menu controller.
ViewController.h
- (void)longPressGestureRecognizer:(UIGestureRecognizer *)recognizer
{
UIMenuItem *mi = [self.label menuItemOpenPleco];
UIMenuController *menuController = [UIMenuController sharedMenuController];
menuController.menuItems = #[mi];
}
PasteboardLabel {h,m}
#interface PasteboardLabel : UILabel
- (UIMenuItem *)menuItemOpenPleco;
#end
#implementation PasteboardLabel
- (UIMenuItem *)menuItemOpenPleco
{
return [[UIMenuItem alloc] initWithTitle:#"Open Pleco" action:#selector(openPleco:)];
}
- (void)openPleco:(id)sender
{
NSString *selectedText = [self textInRange:[self selectedTextRange]];
UIPasteboard *pb = [UIPasteboard generalPasteboard];
pb.string = selectedText;
NSString *urlString = [NSString stringWithFormat:#"plecoapi://x-callback-url/q?s=%#", pb.string];
NSURL *url = [[NSURL alloc] initWithString:urlString];
[[UIApplication sharedApplication] openURL:url];
}
#end
I've found that there's a dearth of examples of adding custom menu items, or explanations of how they work. So I wanted to resolve that by sharing a few important tidbits then showing an example.
The UIMenuController "talks" with UIViews, not with UIViewControllers. This means that your UIMenuController related code needs to go into subclasses of UIView rather than a UIViewController.
Notice the word The at the start of my prior example. There's only one UIMenuController, a singleton which is shared from when your application first starts until it ends. This means that you should only add your item once, and that you shouldn't be writing over the existing array of items.
The appearance of the button in the UIMenu is based on whether or not the UIView that was tapped responds to the selector. This means you need to implement the method if you want the button to appear, and that you don't need to worry about it appearing when unrelated views are tapped unless you pick a selector name for which other UIViews also have methods.
So, having said all that, I made a subclass of a UITextView (which means its a subclass of UIView per my first bullet) and then I gave it this initialize method, along with an implementation for my selector.
+ (void)initialize {
static dispatch_once_t addInsert;
dispatch_once(&addInsert, ^{
UIMenuController *mController = [UIMenuController sharedMenuController];
UIMenuItem *insert = [[UIMenuItem alloc] initWithTitle:#"Insert..."
action:#selector(insert:)];
mController.menuItems = [mController.menuItems arrayByAddingObject:insert];
});
}
- (void)insert:(id)sender {
NSLog(#"Insert... pressed!");
}
The important points above here:
It's in the class initialize method, which is called by the runtime before the first time any other method in your class is invoked. In practice means the code is handled just before the first time an instance of your custom view will be appearing on screen.
I added a dispatch_once guard around it. If my class is subclassed, it's possible that those subclasses will call this initialize method. Maybe those subclasses show up before this one does, so I don't want to prevent the initialize method from running then. I just want to prevent it from running multiple times. Thus why I wrapped the code in a dispatch_once.
I didn't just set the menuItems to a new array of items - I assigned it to a new array of items that extended the existing array of items with my new item.
Hope you find all of that helpful. It's not very complicated, and you can certainly go about implementing my second point in other ways - I tried to pick a way that seemed safest to me, but there are certainly simpler ways of doing it.
I have recently released my first app to the App Store and still have a very long way to go with iOS development.
I'm looking to introduce themes into my app as an update so the user can select from 4-5 different themes.
I've got a tab bar controller and have set the 5th tab to be the "Settings" tab which contains a Table View with cells. The first cell contains the text "Themes" where the user can select it, be taken to a new Table view/Collection View to select the themes.
So I searched online and came across this incredible answer on doing just this:
How to create Multiple Themes/Skins for iphone apps?
Because I'm still new to development, I'm in need of assistance to take this forward.
To start off with, I have two themes:
1) DefaultTheme (Newiphonebackground.png)
2) PurplePinkTheme (Purplepinknew.png)
Following the instructions, I have created one plist for the Default theme and one plist for the PurplePink theme.
In the ThemeManager class that I created, I have:
- (id)init
{
if ((self = [super init]))
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *themeName = [defaults objectForKey:#"theme"] ?: #"DefaultTheme";
NSString *path = [[NSBundle mainBundle] pathForResource:themeName ofType:#"plist"];
self.styles = [NSDictionary dictionaryWithContentsOfFile:path];
}
return self;
}
+ (ThemeManager *)sharedManager
{
static ThemeManager *sharedManager = nil;
if (sharedManager == nil)
{
sharedManager = [[ThemeManager alloc] init];
}
return sharedManager;
}
In my table view, where the theme will get applied (it's going to get applied to every screen in the app but this is just to start off with where I'm testing with one scene in the app), in the viewDidLoad, I put:
NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *imageName = [styles objectForKey:#"DefaultTheme"];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]];
self.tableView.backgroundView = backgroundImageView;
That does nothing different because it applies the default theme. However if in the ThemeManager, I change the plist to be:
NSString *themeName = [defaults objectForKey:#"theme"] ?: #"PurplePinkTheme";
and in the viewDidLoad of the separate Table View, I set the code to be:
NSString *imageName = [styles objectForKey:#"PurplePinkTheme"];
Then upon loading my application, my PurplePinkTheme loads.
So far so good, but I don't think I've done anything substantial here.
I am confused from this point on. I am looking to start off with changing just the background images from the in-app settings and once I have that done, I'll look to change the custom navigation bars, etc.
So my questions are:
1) Why do I create one plist per theme?
2) How exactly do I link multiple plists in the ThemeManager class?
3) How do I link all of the themes up to the settings Theme cells allowing the user to choose?
I'm sorry this is vague, but I really need to understand how exactly I can carry on here. For example, I just don't get how to have multiple plists in the ThemeManager and how to move forward from here.
To recap, I, for now just want the ability to have multiple plist files with the different backgrounds, and for the user to go the settings tab in my app, click on the "Themes" cell and be able to select a different theme for the app without restarting the app.
I know the tutorial does carry on with explanations on that, but I'm just not quite sure I understand it all.
Any guidance on this would be massively appreciated.
Many thanks,
Your question is super long so I must confess I did not read the whole thing. That said I spent a lot of time with themes and the best solution I have found is to create an object that handles formatting. This is how I think you can implement it:
Create a formatter object extending NSObject
Have a property for each changeable piece of the theme:
for example if the background changes images you can have a UIImage in there called background. If the font color changes you have a UIFont property in there.
create a shared instance of your formater by adding:
+(Formater *) sharedInstance; //add to .h
+ (Formater *) sharedInstance //add to .m
{
if (!_sharedInstance)
{
_sharedInstance = [[Formater alloc] init];
}
}
Now in your view controller all you need to do is create a reference to your shared item and use that to style your view controller. (remember to make the changes in viewWillAppear not or it will not change after the setting is changed.
Voila!
According to the OpenGL ES Programming guide's section on texturetool,
Your app must parse the data header to obtain the actual texture data. See the
PVRTextureLoader sample for an example of working with texture data in the PVR format.
This is all well and good, but 3 years later, and PVRTextureLoader's PVRTexture.m doesn't compile because it needs to be converted in order to work in an ARC project. I reckon I could flag these two files as non-ARC but I wanted to at least learn a little about Objective-C this time around.
Here's a bit of code that's giving me trouble at this point:
+ (id)pvrTextureWithContentsOfFile:(NSString *)path
{
return [self initWithContentsOfFile:path];
}
This was manually converted from:
+ (id)pvrTextureWithContentsOfFile:(NSString *)path
{
return [[[self alloc] initWithContentsOfFile:path] autorelease];
}
Maybe someone could be so kind as to walk through what this actually does (as it makes no sense to me to refer to self from what is clearly declared as a class method and not an instance method), but the actual error seen is
<...>/PVRTexture.m:256:15: error: no known class method for selector 'initWithContentsOfFile:'
return [self initWithContentsOfFile:path];
^~~~~~~~~~~~~~~~~~~~~~
1 error generated.
As for why I am manually converting this file to ARC, the Edit->Refactor->Convert to Objective-C ARC... menu option basically says "fix these 8 errors before I can continue" and these 8 errors are of course ARC-related errors. Which I was hoping the conversion would be able to resolve. Circular dependencies are only fun the first time around.
The curious bit is that -initWithContentsOfFile:path is right there in the file too:
- (id)initWithContentsOfFile:(NSString *)path
{
if (self = [super init])
{
NSData *data = [NSData dataWithContentsOfFile:path];
_imageData = [[NSMutableArray alloc] initWithCapacity:10];
_name = 0;
_width = _height = 0;
_internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
_hasAlpha = FALSE;
if (!data || ![self unpackPVRData:data] || ![self createGLTexture])
{
self = nil;
}
}
return self;
}
Note: This code compiles if I change the + to a - on the pvrTextureWithContentsOfFile declaration. I am positive the original code had a + there, so please, somebody help explain this to me.
alloc is a class method. You usually see alloc used from another class, with things like:
NSView *myView = [[NSView alloc] initWithFrame: someRect];
In a class method, self is the class, not an instance of that class. So [self alloc] allocates an instance of the class. It would also be valid to use the name of the class explicitly, so in the case of your PVRTexture class,
return [[self alloc] initWithContentsOfFile:path];
Could be replaced with
return [[PVRTexture alloc] initWithContentsOfFile:path];
Both are perfectly valid. I would probably use the second form, just because, like you, I find the first form a little odd-looking.
I got it, I think. Here's another example where writing out the question leads to the answer.
It's this:
return [[self alloc] initWithContentsOfFile:path];
I'm sure Google will be happy enough to (let SO) show me what alloc actually does. I guess calling [self alloc] from a class method is essentially what makes that method into a factory method that can generate instances of that class. This is nothing other than the Obj-C way to new something.
Me, I made the mistake of assuming alloc was something that ARC abolished.
Also, a side note: Reading carefully helps. I also just found out about GLKTextureLoader so I didn't need to convert PVRTextureLoader at all.
I am trying to display a file using QLPreviewController. The QL view displays correctly (is pushed on top of my Navigation Controller) but the content is blank. However, no errors are displayed and application doesn't crash.
Checks on existence of file return true. (A proof is that if I use [self.docInteractionController presentPreviewAnimated:YES]; where docInteractionController is a UIDocumentInteractionController the file is correctly shown).
The code is taken directly from Apple sample code DocInteraction.
previewController.dataSource = self;
previewController.delegate = self;
// start previewing the document at the current section index
previewController.currentPreviewItemIndex = 0; //I want the first (and only) document
[[self navigationController] pushViewController:previewController animated:YES];
[previewController release];
The current view is a QLPreviewControllerDataSource, QLPreviewControllerDelegate,, and the delegate methods are as follow:
- (NSInteger) numberOfPreviewItemsInPreviewController: (QLPreviewController *) controller
{
return self.documentURLs.count;
}
- (id)previewController:(QLPreviewController *)previewController previewItemAtIndex: (NSInteger)index
{
return [self.documentURLs objectAtIndex:index];
}
documentURLs is a NSArray that contains the fileURLs of the documents. The same fileURL passed to the UIDocumentInteractionController displays correctly. I don't necessarily have to use QuickLook, I may just rely on UIDocumentInteractionController, however the fact that it's not working is really annoying.
Thank you in advance
Giovanni
Make a sample that demoes the issue. If you find that it still occurs on iOS 7, pls file a bug report.
I reported a bug on this class (pass nil URL to get loading indicator) and it got fixed within 2 weeks.