I have an iPad view that has a ScrollView, and below that I'm using a Page Control Object to show what page it is on. Elsewhere on the screen, I have a timeline between say 12:00 AM and 5:00 AM - as time progresses, there is a UIImage that gets wider on top of the timeline to indicate the time of day. The UIImage gets wider as the day goes on through the use of an NSTimer that kicks off every minute or so.
In addition, I have 3 buttons on the page that will refresh the Scrollview with a new set of images inside of it. The number of images can be varied, anywhere between 3 and 7. So, when a button is pressed, I update the scrollview, and also update the Page Control object to set the "numberOfPages" property to the appropriate number.
OK, so the problem seems to be that whenever I click a button and the pageControl numberOfPages is changed, the UIImage reverts back to whatever size it was back when I originally designed it in Interface Builder (the storyboard).
I created a simplified example project that hopefully has enough to recreate the behavior...
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *btnTest;
#property (weak, nonatomic) IBOutlet UIImageView *imgTest;
#property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
- (IBAction)buttonPress:(id)sender;
#end
ViewController.m:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize imgTest,btnTest,pageControl;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Set up an observer to handle updating the timeline
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(UpdateWidth:)
name:#"UpdateWidth" object:nil];
}
-(void)viewDidAppear:(BOOL)animated {
// Send an updateTimeIndicator notification when screen loads.
[[NSNotificationCenter defaultCenter] postNotificationName:#"UpdateWidth" object:#"sent"];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)buttonPress:(id)sender {
pageControl.numberOfPages = 7;
pageControl.currentPage = 0;
}
-(void)UpdateWidth:(NSNotification *)notification{
[imgTest setFrame:CGRectMake([imgTest frame].origin.x, [imgTest frame].origin.y,
450, [imgTest bounds].size.height)];
imgTest.contentMode = UIViewContentModeScaleAspectFit; // This determines position of image
imgTest.clipsToBounds = YES;
[imgTest setNeedsDisplay];
NSLog(#"Width Updated");
}
#end
So, in this example, I have just 3 objects in the Window - the PageControl, a Button, and a UIImage that has the background set to blue. I'm using the Notification Center to send messages as to when to resize the UIImage (in this case, I'm only doing it once, when the view appears). The updateWidth routine sizes the UIImage to be 450 pixels wide, and the view appears as it should.
However, tapping the button will change the numberOfPages value, and in so doing, sets the UIImageView back to the size that it was when it was originally laid out in Interface Builder.
I do have a zipped project file if anyone would like to see it. Also, I did try this without using the Notification Center and the results were the same (I originally wasn't using the Notification Center, just thought it might give different results).
I can probably get by without using the PageControl object, but I'm now very curious as to why happens. Thanks!
I seem to have solved the problem by placing both the ScrollView and the PageControl objects into their own (new) UIView. To do this, I opened the storyboard in interface builder, drug a View object from the toolbox out to the interface window, and then placed both the existing ScrollView and the PageControl objects into the new UIView. Afterwards, changing the numberOfPages property of the PageControl did not affect anything else on the window.
I'm still confused as to this behavior though. I'm guess that because once the page control was placed in a new view and the problem stopped, that the PageControl wants to update the entire view that it is in when the numberOfPages property is changed.
Related
I'm trying to make a configure sheet appear for a ScreenSaverView subclass. After a long battle with Xcode, I'm finally getting the configure sheet to appear when "Screen Saver Options" is clicked in System Preferences (and my screen saver is selected), and the sheet behaves normally with one exception: the background is black, rendering text invisible (see image).
This occurs regardless of whether the sheet is an NSPanel or NSWindow class, and whether the panel is a Regular Panel, Utility Panel, or HUD Panel. The only thing I can seem to do is change is the alpha value of the panel, which as expected makes everything more transparent (but the text is still not visible). Interestingly, calling setOpaque or setBackgroundColor on the NSPanel or NSWindow don't seem to have any effect.
To figure out why it's showing up as black, we'd really need to see code for how you're creating the window.
I just did a quick test project and it seems to work OK here. IMO, the easiest solution for creating the window to return in the configureSheet method is to use an NSWindowController subclass to load a nib file in which you've configured the window ahead of time.
So in your ScreenSaverView subclass, you'd define an interface something like the following:
#interface MDScreenSaverFinaglerView : ScreenSaverView {
MDScreenSaverOptionsWindowController *optionsWindowController;
NSInteger screenSaverViewMode;
}
#property (nonatomic, retain) MDScreenSaverOptionsWindowController
*optionsWindowController;
#property (nonatomic, assign) NSInteger screenSaverViewMode;
#end
Your implementation would then look like this for the configureSheet method:
- (NSWindow *)configureSheet {
if (optionsWindowController == nil) {
optionsWindowController = [[MDScreenSaverOptionsWindowController alloc]
initWithScreenSaverView:self];
}
return optionsWindowController.window;
}
Basically, you check to see if the optionsWindowController instance exists, creating it if necessary, then return its window.
The interface for the custom NSWindowController subclass would look like the following:
#interface MDScreenSaverOptionsWindowController : NSWindowController {
IBOutlet NSMatrix *optionsMatrix;
MDScreenSaverFinaglerView *screenSaverView; // non-retained/weak reference
}
- (id)initWithScreenSaverView:(MDScreenSaverFinaglerView *)aView;
#property (nonatomic, assign) MDScreenSaverFinaglerView *screenSaverView;
- (IBAction)ok:(id)sender;
#end
There's a screenSaverView property which will allow communication back with the ScreenSaverView subclass once the user has clicked the OK button.
The nib file for the MDScreenSaverOptionsWindowController class (named "MDScreenSaverOptionsWindowController.xib") is set up like shown below:
The implementation of the MDScreenSaverOptionsWindowController looks like the following:
#implementation MDScreenSaverOptionsWindowController
#synthesize screenSaverView;
- (id)initWithScreenSaverView:(MDScreenSaverFinaglerView *)aView {
NSParameterAssert(aView != nil);
if ((self = [super initWithWindowNibName:NSStringFromClass([self class])])) {
self.screenSaverView = aView;
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
[optionsMatrix selectCellWithTag:screenSaverView.screenSaverViewMode];
}
- (IBAction)ok:(id)sender {
NSInteger viewMode = [optionsMatrix selectedTag];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber
numberWithInteger:viewMode] forKey:MDScreenSaverViewModeKey];
screenSaverView.screenSaverViewMode = viewMode;
[NSApp endSheet:self.window];
}
#end
The end result:
Sample project: ScreenSaverFinagler.zip
I'm having a hard time implementing a simple scroll on my detail view.
The app is straightforward with a Master and Detail views.
When the user taps an item on Master, the Detail view is pushed with larger photo, blog text and etc.
I would like the entire Detail view to scroll, so if the picture is tall, or the text is long, they can scroll vertically to see/read more. I do not want the user to scroll these items individually. It should feel like webpage scrolling.
Currently my Detail view loads OK but I can't make it scroll.
My DetailViewController.h
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController {
IBOutlet UILabel *postTextLabel; // wired to Text Label
IBOutlet UILabel *postAuthorNameLabel; // wired to Author Label
}
#property (strong, nonatomic) id detailItem;
#end
My DetailViewController.m
#import "DetailViewController.h"
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self configureView];
}
- (void)configureView
{
if (self.detailItem) {
NSDictionary *post = self.detailItem;
NSString *postText = [post objectForKey:#"post_text"];
NSString *postAuthorName = [post objectForKey:#"post_author_name"];
postTextLabel.text = postText;
postAuthorNameLabel.text = postAuthorName;
}
}
#end
Structure on IB:
Any ideas on what's missing to make this work?
I would do the following:
1)(optional)turn your View into a scrollview by dragging it into the view in the scruture list on the side.
2)link the scrollView into your viewcontroller .h and make an Outlet connection something like this
#property (strong, nonatomic) IBOutlet UIScrollView *scroller;
(make sure you add #synthesize in the .m if you add this manually)
and make sure it is connected in IB!
3)set the contentsize of the scrollview in viewDidLoad method
scroller.contentSize = CGSizeMake(320,550);
NOTE: IBOutlet UILabel *postTextLabel; should actually probably be a UITextView so you can access ContentSize
Which would allow for the following.
CGRect frame = postTextLabel.frame;
frame.size = postTextLabel.contentSize;
postTextLabel.frame = frame;
scroller.contentSize = CGSizeMake(320, frame.size.height+200);//200 or how ever much space is above the textView
and again that only works if you use a UITextView
for ipad replace 320 with 768 or 1024, or whichever depending on orientation
The best way to connect it in IB is like this holding down control and dragging to the .h file
and make sure it is set to automatic like in the picture, and pointing to> the .h of your view.
Adding it like this also automatically adds the #synthesize for you.
Make sure UserInteractionsEnabled are checked here for our scrollview
I have a TextViewController which is a subclass of NSViewController which controls a View containing a NSTextView
I attach an instance of TextViewController to an existing view with the following code
TextViewController *textViewer;
...
[[self view] addSubview:[textViewer view]]; // embed new TextView in our host view
[[textViewer view] setFrame:[[self view] bounds]]; // resize the controller's view to the host size
textViewer.delegate = self;
[split.window makeFirstResponder:textViewer];
This works quite well, and the TextViewController traps the keyDown Event to perform various actions.
I wanted to allow users to select text in the NSTextView to copy to clipboard.
Unfortunately clicking in the NSTextView makes this the FirstResponder and stops TextViewController from responding to key presses.
I can force the FirstResponder back to my TextViewController, but this seems like a kludge.
- (void)textViewDidChangeSelection:(NSNotification *)aNotification {
[self.view.window makeFirstResponder:self];
}
I know I could subclass NSTextView to trap the keyDown Event, but this doesn't seem much better.
I am sure there must be a more elegant way of doing this.
I added a subclass of NSTextView which just passes the keyDown to the controller
#protocol MyTextViewDelegate
- (BOOL)keyPressedInTextView:(NSEvent *)theEvent;
#end
#interface MyTextView : NSTextView
#property (assign) IBOutlet NSObject <MyTextViewDelegate> *delegate;
#end
...
#implementation MyTextView
#synthesize delegate;
- (void)keyDown:(NSEvent *)theEvent {
if([self.delegate respondsToSelector:#selector(keyPressedInTextView:)]) {
if([self.delegate keyPressedInTextView:theEvent])
return;
}
[super keyDown:theEvent];
}
#end
Subclassing NSTextView sounds like the best solution for the problem you described. You want a text view that behaves like normal to select, copy, Cmd-A, etc. except that it also responds to special key presses you've defined. This is a standard use of a subclass. Trying to have the view controller handle things by playing games with the first responder will give you problems in various edge cases like the one you discovered.
Absolutely beginner question. I am trying to make an app which will switch an array of images by swiping the screen sideways. So, as vanilla case of that I was trying to display an image first and then think about how to switch between images using an action. I am using XCode 4.2.
So, here's what I have so far. I have added a UIImageView to my storyboard, then Ctrl+dragged into the ".h" file to create an Outlet and it looks something like this:
SwitchViewController.h
#import <UIKit/UIKit.h>
#interface SwitchViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#end
And then in the ".m" file, I am trying to set an image to the UIImageView in the DidLoad method.
SwitchViewController.m
#import "SwitchViewController.h"
#implementation SwitchViewController
#synthesize imageView;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImage *plate1 = [UIImage imageNamed:#"plates1.tif"];
[imageView setImage:plate1];
}
- (void)viewDidUnload
{
[self setImageView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ((interfaceOrientation == UIInterfaceOrientationLandscapeRight) ||
(interfaceOrientation == UIInterfaceOrientationLandscapeLeft))
return YES;
return NO;
}
#end
Now, if I try to run this, there are no compilation errors. But the program halts saying, Thread 1:Program received signal "SIGABRT"
Referring to your actual problem, you're getting this exception being thrown:
2012-01-26 22:04:40.728 Switch-a-Switch[548:f803] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<SwitchViewController 0x6840660> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key UIImageView.'
It would appear that you've wired up the UIImageView to a property called UIImageView rather than imageView. Go and redo the connection in Interface Builder and make sure you drag from the imageView property to your UIImageView instance.
UIImage *plate1 = [UIImage imageNamed:#"plates1.tif"];
[imageView setImage:plate1];
should be kosher in iOS and 64 Bit (non-fragile ABI environments, as the iVar will be synthesized), I think it is much better form to include the ivar in the interface....
#interface SwitchViewController : UIViewController
{
UIImageView *imageView;
}
The thing that you should do to track this down is to turn on zombies and enable an exception breakpoint.
afew things there. (now im only learning myself so may be wrong but here goes)
when you use the #property u should use the self.imageView setter to assign to it. so [imageView setImage:plate1] would become self.imageView.image = plate1. and change it from weak to retain in the .h. and in the viewdidunload do self.imageView = nil. also i think the swiping of images effect your trying to achieve here might be better suited to a horizontal scrollview. and u can turn on paging or something for that.
hope that helps and didnt send u completely wrong direction :P
I think you may be going about this the wrong way. If you want to side swip to go between images, you should use a UIScrollView with paging enabled. A quick google came up with this.
Hope that helps
I have a question about using NSViewController and switching between views. I have a Cocoa application where I have a window. The idea with the window is that it will display a number of views one by one where each view is stored in a separate XIB file. Each view has a corresponding NSViewController. I have made a minimal example of what I'm doing where only the first view is loaded.
#interface MyWindowController : NSWindowController {
NSViewController *currentViewController;
}
#property (assign) IBOutlet NSView *targetView;
#end
#implementation MyWindowController
#synthesize targetView;
- (id)init
{
return [super initWithWindowNibName:#"MyWindow"];
}
- (void)dealloc
{
[currentViewController release];
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
currentViewController = [[NSViewController alloc] initWithNibName:#"FirstView" bundle:nil];
[self.targetView addSubview:currentViewController.view];
[currentViewController.view setFrame:targetView.bounds];
}
#end
When the window is loaded the view from FirstView.xib is also loaded and the view is displayed in the window. In this case the loaded view only has a text field and I would like the text field to be highlighted so that input can be written to it directly without the user having to click on it but I can't figure out how to do that. Is it possible to have the text field selected when the view is loaded?
After reading the documentation I have found that I probably want to set the window's initialFirstResponder to the text field, but I can't find how to do that when the text field is in a different XIB file than the window.
Whenever you add/replace a subview and want it to be the first responder, have your window controller make the view the first responder of the window managed by the window controller:
[[self window] makeFirstResponder:currentViewController.view];
You’ll want to do this in both -windowDidLoad and whichever other methods add/replace subviews.