I have a UIViewController,which is associated with custom class MAViewControllerMenu and loads right after the splash screen. In that UIViewController, I have an UIScrollView, which belongs to another class, MASlideShowView, in which the IBOutlet of the UIScrollView is defined and is connected to.
The class for the UIViewController has, among others, the field:
#property MASlideShowView* slideShow;
as a private property for the class that holds the UIScrollView inside it.
Also in the UIViewController,
- (void)viewDidLoad
{
[super viewDidLoad];
//TODO [_slideShow initializeImages];
_slideShow = [[MASlideShowView alloc] initWithModel];
_slideShow.delegate = imageViewController;
}
- (void)viewDidAppear{
[super viewDidAppear:(YES)];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Set up the content size of the scroll view
//HERE, self.slideShow is allocated, but all the fields it has, including the IBOutlet to the UIScrollView is still nil
CGSize pagesScrollViewSize = self.slideShow.frame.size;
_slideShow.contentSize = CGSizeMake(pagesScrollViewSize.width * self.pageViews.count, pagesScrollViewSize.height);
//Delegate
_slideShow.scrollView.delegate = self;
// Load the initial set of pages that are on screen
[_slideShow loadVisiblePages:YES page_index:0 image:_last_image_taken];
}
Note the error I saw in the comments in the above class
The MASlideShowView file looks like:
h:
#class MASlideShowView;
#protocol slideShowDelegate <NSObject>
-(void)imageViewSelected:(MASlideShowView*)slideShow image:(UIImage*)image;
#end
#interface MASlideShowView : UIScrollView
#property (nonatomic,weak) id<slideShowDelegate> delegate;//delegate to next controller to notify upon picture centered
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) IBOutlet UIPageControl *pageControl;
#property (weak, nonatomic) IBOutlet UIButton *rotateImageButton;
#property UIImageView* centered_image_view;
- (IBAction)PageThroughPageControl;
- (IBAction)rotateImageButtonClicked;
- (id)initWithModel;
- (void)pageThroughPageControl;
- (void)addImageToSlideshow:(UIImage*)toAdd;
- (void)loadVisiblePages:(BOOL)use_page_number page_index:(NSInteger)page image:(UIImage*)image;
#end
m:
- (id)initWithModel{
[self initializeImages];
return self;
}
-(void)initializeImages{
// Set up the image you want to scroll & zoom and add it to the scroll view
self.pageViews = [NSMutableArray arrayWithObjects:nil];
NSInteger pageCount = 0;
_imageViewCount = 0;
// Set up the page control
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = pageCount;
// Set up the array to hold the views for each page
self.pageViews = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < pageCount; ++i) {
[self.pageViews addObject:[NSNull null]];
}
}
My question is simple:
How can I make the UIScrollView initialize?
I know that there's no viewDidAppear as it inherits from UIScrollView.
Thanks
As you are using Interface Builder, I would recommend calling initializeImages inside awakeFromNib:
An awakeFromNib message is sent to each object loaded from the
archive, but only if it can respond to the message, and only after all
the objects in the archive have been loaded and initialized. When an
object receives an awakeFromNib message, it is guaranteed to have all
its outlet instance variables set.
More details here.
Other observations:
As for your code, you have slideShow correctly set by Interface Builder when entering viewDidLoad but you're replacing that instance by assigning _slideShow = [[MASlideShowView alloc] initWithModel], which results in a completely different object.
Moreover your initWithModel doesn't look at all like a correct init method as it doesn't call any of its super's init methods. You should start with Apple's snippet by writing init in an empty line and press escape:
Again the first paragraph of the answer should be enough for your problem.
There's a few ways you could go about fixing this.
One way is like #HoanNguyen mentioned to use awakeFromNib. Personally I don't use this but it's a valid lifecycle event for setup.
Another option is to override initWithCoder: which is the standard initializer storyboards use
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
[self initializeImages];
}
return self;
}
You could then remove your initWithModel call and the storyboard should handle everything.
Related
I've managed to setup a custom UIView class with a nib.
My .h looks like
#interface MyView : UIView <UITextFieldDelegate>
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) MyView *topView;
And .m
#implementation MyView
NSString *_detail;
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
MyView *v = [[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if (self.topView == nil)self.topView = self;
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
-(NSString *)topDetail{
return _detail;
}
-(NSString *)detail{
return [self.topView topDetail];
}
-(void)setTopDetail:(NSString *)detail{
_detail = detail;
}
-(void)setDetail:(NSString *)detail{
[self.topView setTopDetail:detail];
}
- (BOOL)textFieldShouldReturn{
//here I show an UIAlertView using self.detail for the message
}
Note: The setup I have works exactly how I want it to.
The problem
What I would like to do is remove my manual detail methods and turn NSString *_detail into #property (...)NSString *detail
When I try it with the #property, then within my ViewController if i call
myView.detail = someString, myView will be referring to the top most view. Then if textFieldShouldReturn gets called because of user interaction, then it calls the nested MyViews _detail which has not been set.
What I want:
To not have to write extra code for access to _detail regardless of where I'm accessing it from. I want to merely declare the property and go on with my usual coding.
Your problem is that you're trying to keep the a class reference, topView, with an object property.
In other words every objects' topView is the object itself, which makes no sense.
Your definition should be:
#interface MyView : UIView <UITextFieldDelegate>
// Class "properties"
+ (instancetype)topview;
+ (void)setTopView:(UIView *)topView;
// Object properties
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) NSString *detail;
Now you can keep track of the topView:
static MyView * _topView;
#implementation MyView
+ (instancetype)topView {return _topView}; // You could also create one here lazily
+ (void)setTopView:(UIView *)topView { _topView = topView };
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
JUITextFieldHint *v = [[[NSBundle mainBundle] loadNibNamed:#"JUITextFieldHint" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if ([MyView topView] == nil)[MyView setTopView:self];
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
No more need for manual setters and getters. Now you can use your detail property, either with anyInstance.detail or [MyView topView].detail, or even MyView.topView.detail if you like dots like me ;)
You're init method still looks weird but should work. Check Apples init template.
Lastly, textField can be weak as long as it has a superview, otherwise make it strong.
My xib contained one UIView (no controller). I had the UIView set to MyView for the class.
I changed the UIView back to just UIView then set File's Owner to MyView. This solved issues of recursion (which is why I had such a weird setup in the first place) and caused my variables and IBOutlets to be linked up properly.
Credit goes to How do I create a custom iOS view class and instantiate multiple copies of it (in IB)? and some of the comments which I missed the first couple times I read through it.
I have a UIViewController which I placed UISegmenetedControl with 2 options and beneath I have a UIView which acts as a container for putting my custom UIView (that is actually a UITableView). When switching between segments I would like to switch between 2 different UITableViews.
My problem is with the UITableView.
I have created a custom UIView class with .xib and inside I put a UITableView and I'm able to populate the data into the table and see it correctly.
The problem is with the scrolling, it doesn't react to vertical scrolling at all!
Here is how I created the UIView with its table.
.h file
#interface LeaderboardTableView : UIView
#property (strong, nonatomic) IBOutlet UIView *view;
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSDictionary *myScore;
#property (strong, nonatomic) NSArray *players;
- (id)initWithBoardType:(LeaderboardType)boardType myScore:(NSDictionary*)myScore leaderboardData:(NSArray*)data;
#end
.m file
#implementation LeaderboardTableView
- (id)initWithBoardType:(LeaderboardType)boardType myScore:(NSDictionary*)myScore leaderboardData:(NSArray*)data {
self = [super init];
if(self) {
_players = data;
_myScore = myScore;
_boardType = boardType;
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self) {
[self setup];
}
return self;
}
- (void)setup {
[[NSBundle mainBundle] loadNibNamed:#"LeaderboardTableView" owner:self options:nil];
[self addSubview:self.view];
}
Here is my .XIB
What am I doing wrong?? I suspect that my UITableView resides in UIView and that's why I can't scroll but I cannot figure out how to solve this.
Thank you!
Assuming that you use initWithBoardType:myScore:leaderboardData: to instantiate your view, try to change :
self = [super init];
by
self = [self initWithNibName:#"LeaderboardTableView" bundle:[NSBundle mainBundle]];
in this method.
But it's not sure that it will fix your scrolling problem. It looks like if there were a "invisible" view over your table. Let me know how you display your view.
A better idea would be to use one table view and switch out the data source for each different UISegmentedControl tap.
Finally I was able to resolve this problem with a lot of help from zbMax !
Eventually I made my custom Table to subclass UITableViewController with XIB. I implemented all the logic of populating cells and embedded this TableView in my parent view controller, this way I could switch between 2 Views of tables.
I am attempting to create a custom subclass of a UIView as follows:
I created a .xib with a UIView that contains a Picker object and Toolbar object, hooked up the Outlets and actions.
CustomPickerView.h
#import <UIKit/UIKit.h>
#interface CustomPickerView : UIView
#property (strong, nonatomic) IBOutlet UIDatePicker* datePicker;
#property (strong, nonatomic) IBOutlet UIBarButtonItem* doneButton;
-(IBAction) buttonDonePush:(id)sender;
#end
CustomPickerView.m
#import "CustomPickerView.h"
#implementation CustomPickerView
-(id) init
{
self=[[[NSBundle mainBundle] loadNibNamed:#"CustomPickerView" owner:self options:nil] objectAtIndex:0];
return self;
}
-(void) buttonDonePush:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"CustomPickerViewDoneButtonPush" object:nil userInfo:[NSDictionary dictionaryWithObject:self.datePicker.date forKey:#"date"]];
}
#end
And finally, in my ViewController I instantiate the object in the viewDidLoad method.
- (void)viewDidLoad
{
[super viewDidLoad];
self.customPickerView=[[CustomPickerView alloc] init];
self.customPickerView.datePicker.datePickerMode=UIDatePickerModeTime;
self.dateField.inputView=self.customPickerView;
}
When the user taps on the self.dateField, my CustomPickerView pops up nicely in place of the standard keyboard.
The problem is when the user taps the Done button from my CustomPickerView class, the buttonDonePush action does not fire.
This answer can be considered as the iOS companion to a similar solution I offered recently for iOSX:
Interface-Builder: "combine" NSView-class with .xib
Your arrangement is thus:
Mainstoryboard.storyboard
MyViewController.h
MyViewController.m
CustomPickerView.xib
CustomPickerView.h
CustomPickerView.m
You want to use your customPickerView as a subview of MyViewController.view and want to be able to access it's control widgets from the containing context.
In your example you are creating the customPickerView in code, but another useful scenario is to add it to the storyboard in Interface Builder. This solution will work for both scenarios.
In CustomViewPicker.h
declare IBOutlets for your interface elements. You have already done this for your datePicker and doneButton, but you also need an IBOutlet to a UIView which will be the containing view for these items.
#property (strong, nonatomic) IBOutlet UIView* view;
In CustomViewPicker.xib
Set the file's owner class to CustomViewPicker in the Identity Inspector.
Set the top-level view in the xib to the defaul UIView class (NOT CustomViewPicker).
Connect your IBOutlets from the file's owner: view, datePicker, doneButton to their respective IB objects
Connect your IBAction from the file's owner: buttonDonePush to the doneButton IB object
In CustomViewPicker.m:
- (id)initWithFrame:(CGRect)frame
{
//called when initialising in code
self = [super initWithFrame:frame];
if (self) {
[self initialise];
}
return self;
}
- (void)awakeFromNib
{
//called when loading from IB/Storyboard
[self initialise];
}
- (void) initialise
{
NSString* nibName = NSStringFromClass([self class]);
if ([[NSBundle mainBundle] loadNibNamed:nibName
owner:self
options:nil]) {
[self.view setFrame:[self bounds]];
[self addSubview:self.view];
}
}
-(void) buttonDonePush:(id)sender
{
//button push actions
}
If you want to initialise in code (as you have done), your MyViewController would contain something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect frame = CGRectMake(0, 50, 320, 300);
self.customPickerView=[[CustomPickerView alloc] initWithFrame:frame];
self.customPickerView.datePicker.datePickerMode=UIDatePickerModeTime;
self.dateField.inputView=self.customPickerView;
}
[edit removed this redundant line: [self.view addSubview:self.customPickerView];]
Alternatively you can create your CustomPickerView - and set it's frame - directly in the storyboard. Just add a custom view to your MyViewController's storyboard scene, and change it's class to CustomPickerView. Link it to your self.customPickerView IBOutlet.
In this case initWithFrame does not get called, but awakeFromNib is invoked when MyViewController loads it's CustomPickerView subview. Your MyViewController's viewDidLoad would then look like this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.customPickerView.datePicker.datePickerMode=UIDatePickerModeTime;
self.dateField.inputView=self.customPickerView;
}
If you want to get your button push action out of the customPickerView, you might consider using a delegate, which could be more self-contained than your use of NSNotification (but that issue reaches beyond your original question).
EDIT:
An answer above pointed this out, but in the init method you are setting self, but this happens before self is ever initialized. If you could show the code where you are creating this specific view, it would help a lot. Here's my suggestion.
In your class that is controlling the deployment of this custom view:
//to invoke your view
CustomPickerView *myView;
NSArray *xibContents = [[NSBundle mainBundle]loadNibNamed:#"CustomPickerView" owner:nil options:nil];
for (id xibObject in xibContents) {
if ([xibObject isKindOfClass:[CustomPickerView class]]) {
myView = (CustomPickerView *)xibObject;
break;
}
}
//now *myView is instantiated as your custom picker view
//do what you want here, add to subview, set frame, etc
In the CustomPickerView.m file, remove the init method.
PREVIOUS ANSWER:
You are using NSNotificationCenter in this implementation. When a user touches the done button, an NSNotification is posted. You must explicitly "opt in" and "listen" for these notifications. You do that by registering with the notification center.
In viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedNotification:)
name:#"CustomPickerViewDoneButtonPush"
object:nil];
Then you need to implement the selector you specified up there:
-(void)receivedNotification:(NSNotification *)note {
NSDictionary *obj = [note object];
NSLog(#"%#",obj);
//dismiss the date picker here...
//etc...
}
I am quite new to iOS development and thus new to the concept of storyboard as well.
As this seems to be the 'new thing', everyone should use, I thought I might give it a try as well.
I got a project here, created with a Foo.xib file.
The xib file has several view objects included.
Then I have a class Foo.h and Foo.m class with following content:
Foo.h
#import <UIKit/UIKit.h>
#interface Foo : UIView
#property (strong, nonatomic) IBOutlet UIView *view01;
#property (strong, nonatomic) IBOutlet UIView *view02;
#property (strong, nonatomic) IBOutlet UIView *view03;
#property (strong, nonatomic) IBOutlet UIView *view04;
- (NSUInteger)viewCount;
#end
Foo.m
#import "Foo.h"
#interface Foo()
#property (nonatomic, strong) NSArray *views;
#end
#implementation Foo
- (id)init {
self = [super init];
if (self) {
self.views = [[NSBundle mainBundle] loadNibNamed:#"Foo" owner:self options:nil];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (NSUInteger)viewCount {
return [self.views count];
}
#end
In my ViewController I would then load all the views and make it scrollable, like this:
#import "ViewController.h"
#import "Foo.h"
#interface ViewController ()
#property (nonatomic, strong) UIScrollView *scrollView;
#property (nonatomic, strong) Foo *views;
#end
#implementation ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.views = [[Foo alloc] init];
CGSize fooSize = self.views.view01.bounds.size;
NSUInteger viewCount = [self.views viewCount];
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, fooSize.width, fooSize.height)];
[self.scrollView setContentSize:CGSizeMake(viewCount*fooSize.width, fooSize.height)];
[self.scrollView setBounces:YES];
[self.scrollView setPagingEnabled:YES];
self.scrollView.delegate = self;
NSArray *views = #[ self.views.view01,
self.views.view02,
self.views.view03,
self.views.view04
];
for (int i=0; i<viewCount; i++) {
UIView *curView = views[i];
CGRect frame = curView.frame;
frame.origin.x = i*fooSize.width;
frame.origin.y = 0;
curView.frame = frame;
[self.scrollView addSubview:curView];
}
[self.view addSubview:self.scrollView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
However, I have no clue, how to realize this with a storyboard. It seems to me that I have to have a NavigationController which is then linked to the Master View Controller. And now I would have to add a new ViewController for each view? Or is there a way to include all views within one ViewController like I did 'the old way'?
There is a massive mis conception that when using a storyboard it limits you to what you can do. A storyboard is simply like an array of .xib files, it holds many screens in the one file so you can see the entire flow of you app in one place. Inside a storyboard you can create a single viewController and assign a custom viewController class to it and then load / modify what ever you like inside the code of this viewController, as you have done above.
However the benefit of using the storyboard is to have multiple viewController objects so you can design all the screens and navigation there were you can see it, aiding you in debugging and design work.
If you already have the app working without a storyboard and you simply want to use it because its new but keep the old style of coding, you are not going to see much of the benefits. I would suggest following this example from the developer library on how to use a storyboard properly. After you complete this you will see the benefits of the new style and you can decide whether to do it in code or using the interface builder:
http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/SecondiOSAppTutorial/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011318
I have create a UIVeiw class and a .xib. Within this .xib view I have its set to freeform with the dimensions of 400x200 and I have assigned it to my custom class with the same name:
Storyboard: blogView
Class Files: blogView.h & blogView.m
Within in the .xib i have added a label and a text field and linked them up to variable within the .h files etc (See code below).
blogCont.h
#import <UIKit/UIKit.h>
#interface blogCont : UIView
#property (strong, nonatomic) IBOutlet UILabel *lbBlogDate;
#property (strong, nonatomic) IBOutlet UITextView *txtBlogTitle;
#end
blogCont.m
#import "newsStoryView.h"
#implementation blogCont
#synthesize lbBlogDate;
#synthesize txtBlogTitle;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code place a log to see if it loads
NSLog(#"View Loaded");
}
return self;
}
#end
Now with in my main viewController.m file i have added the following code to init this view class, and I have added a background colour to see if this loads in.
viewController.m
UIView *blogCont = [[blogView alloc] init];
blogCont.backgroundColor = [UIColor purpleColor];
[subview addSubview:blogCont];
Now when I run this it all works well but as I do not see the purple background it looks as if the view does not load, but within the log I do see the NSLog message I have within this view NSLog(#"View Loaded"); so it seems it initiating this, but I cannot for the life of me get this to display?
Now if I change the code slightly to my main View Controller.m fiel to:
CGRect blogFrame;
blogFrame.origin.x = 20;
blogFrame.origin.y = 20;
blogFrame.size = CGRectMake(400,200);;
newsStoryView *blogCont = [[blogView alloc] blogFrame];
blogCont.backgroundColor = [UIColor purpleColor];
[subview addSubview:blogCont];
Then I get my view display a nice purple box, so this shows up when I set a frame size and the init the view with it 'blogFrame', bu tI thought that all this would be set within the .xib settings so no need to do this?
SO how can I create this external view class and assign it into another view and then manipulate its data, as accessing the label in the .xib using blogCont.lbBlogDate.text does not seem to work that is it probably does but as I cannot view it i cannot confirm it.
What am i doing wrong?
Thanks
Seems I nearly answered my own question then did:
I was not setting the size within my separate class view I was asking for a size when init it:
- (id)initWithFrame:(CGRect)frame
this is asking for a size
so I could do the following to the above:
- (id)init
{
self = [super initWithFrame:CGRectMake(0, 0, 478, 220)];
.... rest of code
Setting the size within the view load.
But I could also set it when I init it in my main view controller as below:
newsStoryView *blogCont = [[blogView alloc] initWithFrame:CGRectMake(0, 0, 400, 200)];
This is better as I can control the position of each one. Hope this helps anyone