I have a view controller with a few buttons, labels and 2 views. One view is a background. The other is a puzzle on top of it. Everything is in perfect working order. I know how to randomly create the puzzles keeping just one background. What I want to do is hit a button and have a new puzzle with its matching background. I want all other functions on the page to stay the same. Just a new puzzle with it's matching background. Any help would be greatly appreciated.
I'll leave the details of the implementation for you to fill in.
You need to be able to correlate your views. You can do this with strictly-managed arrays or with simple composition objects or with direct relationship.
strictly-managed arrays
NSArray *puzzles = #[/*...*/];
NSArray *backgrounds = #[/*...*/];
- (IBAction)changePuzzle {
NSUInteger index = arc4random_uniform([puzzles count]);
UIView *puzzleView = puzzles[index];
UIView *backgroundView = backgrounds[index];
// Update UI with puzzleView and backgroundView
}
simple composition object
#interface MatchedView : NSObject
#property (strong) UIView *puzzle;
#property (strong) UIView *background;
#end
NSArray *matchedViews = #[/*...*/];
- (IBAction)changePuzzle {
NSUInteger index = arc4random_uniform([matchedViews count]);
UIView *puzzleView = [matchedView[index] puzzleView];
UIView *backgroundView = [matchedView[index] backgroundView];
// Update UI with puzzleView and backgroundView
}
direct relationship
#interface PuzzleView : UIView
#property (strong) UIView *backgroundView;
/* ... */
#end
NSArray *puzzleViews = #[/*...*/];
- (IBAction)changePuzzle {
NSUInteger index = arc4random_uniform([puzzleViews count]);
PuzzleView *puzzleView = puzzleViews[index];
UIView *backgroundView = [puzzleView backgroundView];
// Update UI with puzzleView and backgroundView
}
Make those IBOutlets as you see fit. You can replace the array in any of those cases with any other way that you have to look up the views you want to randomly choose among (i.e. using tag).
Related
I am struggling with an issue which I simply do not understand. Here is the following scenario:
I try to create a multi-view wizard in iOS. After much consideration I decided to go with an approach that may not be ideal but will help me keep the code clean. I don't want to chain ten view controllers and manage the manually.
The wizard has one view controller. At start it loads a certain amount of UIView's into the view and allows me to scroll left and right. Code below.
The UIViews are created in interface builder and I have one XIB file containing all the views.
Each UIView has more IBOutlets (for example a label) which are connected to the File Owner, in this case the wizard view controller.
This all works really well EXCEPT for the fact that the labels on the loaded UIViews don't update. Here is the problem:
I call the wizard and it loads all UIViews as well as Outlets right then
I skip through the wizard.
But then the problem occurs:
Example:
Step 2 has a button to call the address book (which works)
The Address book opens, returns an object (which works)
But my Label on UIView "Step 2" doesn't update when I assign a value.
Only after I remove this UIView from the Wizard and add it back, it works. It seems to me that the UIView loads correctly but any subsequent changes to their outlets don't work.
By the way, I verified that the label as well as the buttons and everything is connected probably. I just cannot update the label when its loaded as part of the XIB and the value changes after the initial load.
Code:
Wizard loading UIViews:
-(void)configureWizard
{
self.showsHorizontalScrollIndicator = NO;
self.showsVerticalScrollIndicator = NO;
self.scrollEnabled = NO;
self.contentSize = CGSizeMake(CGRectGetWidth(self.frame) * [wizardDelegate numberOfSteps], CGRectGetHeight(self.frame));
self.delegate = self;
}
-(void)loadWizard
{
[self configureWizard];
[self updateNavButtonsIfNecessary];
currentIndex = 0;
for (int i = 0; i<[wizardDelegate numberOfSteps]; i++)
{
[self loadWizardStepAtIndex:i];
}
}
- (void)loadWizardStepAtIndex:(int)index {
CGRect frame = self.frame;
frame.origin.x = CGRectGetWidth(frame) * index;
frame.origin.y = 0;
UIView *view = [wizardDelegate viewForStepAtIndex:index];
view.frame = frame;
[self addSubview:view];
}
Wizard Main View Controller.h (File Owner)
#property (strong, nonatomic) IBOutlet UIView *memberwizard_1;
#property (strong, nonatomic) IBOutlet UIView *memberwizard_2;
#property (strong, nonatomic) IBOutlet UIView *memberwizard_3;
#property (strong, nonatomic) IBOutlet UIView *memberwizard_4;
#property (strong, nonatomic) IBOutlet UIView *memberwizard_5;
#property (strong, nonatomic) IBOutlet UIView *memberwizard_6;
#property (strong, nonatomic) IBOutlet UIView *memberwizard_7;
#property (strong, nonatomic) IBOutlet UIButton *wizard2_lookupExistingContact;
#property (strong, nonatomic) IBOutlet UILabel *wizard2_nameLabel;
Wizard Main View Controller.m (File Owner)
-(UIView *)viewForStepAtIndex:(int)index
{
[[[NSBundle mainBundle]loadNibNamed:#"wizard_newMember" owner:self options:nil] lastObject];;
switch (index) {
case 0:
return self.memberwizard_1;
break;
case 1:
return self.memberwizard_2;
break;
case 2:
return self.memberwizard_3;
break;
case 3:
return self.memberwizard_4;
break;
case 4:
return self.memberwizard_5;
break;
case 5:
return self.memberwizard_6;
break;
case 6:
return self.memberwizard_7;
break;
default:
return self.memberwizard_1;
}
}
Return function of the address book
-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person
{
[self displayPersonRecord:person];
[self dismissViewControllerAnimated:YES completion:nil];
}
Trying to update the label on the second UIView (this is where the issue occurs - it doesnt work).
-(void) displayPersonRecord:(ABRecordRef)person
{
NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(person, kABPersonLastNameProperty);
NSLog(#"Name is %#", [NSString stringWithFormat:#"%# %#", firstName, lastName]);
self.wizard2_nameLabel.text = [NSString stringWithFormat:#"%# %#", firstName, lastName];
[self.wizardDelegate refresh:self.memberwizard_2]; //if called, the current view will be removed and re-added - then the label gets updated.
}
But as I mentioned if I reload the view then it works (see screenshot)
Reload method IMPORTANT: this is nasty code and not used in production, its just a test to see if a reload of the view helps.
-(void)refresh:(UIView *) oldView
{
[oldView removeFromSuperview];
CGRect frame = self.frame;
frame.origin.x = CGRectGetWidth(frame) * currentIndex;
frame.origin.y = 0;
//UIView *view = [wizardDelegate viewForStepAtIndex:currentIndex];
oldView.frame = frame;
[self addSubview:oldView];
}
If at this point I remove self.memberwizard_2 view from the wizard and add it back in everything works but it doesn't right away. Could that be related to way I initiate the UIViews? By the way the NSLog in the last paragraph works as well showing the name of the contact.
In case anyone knows an easier way to deal with wizards let me know :) PageViewControllers are painful because I need to hand-over one object from screen to screen but need to display different actions in each screen.
Thanks much and apologies for the code mess, I tried to make it as clear as possible but maybe it isn't.
Here's where I think it goes wrong. For each "WizardStep", you call viewForStepAtIndex which loads the NIB and returns the relevant view. You then add that as a subview. But when you process the next WizardStep, the NIB gets loaded again, which creates new instances of all its objects and reconnects all the Outlets and Actions to the newly loaded objects. So the view you have just added as a subview (and so is displayed) is no longer pointed to by your outlets. Thus when you update the label, it is not updating the label on screen, but a completely different label on a view that's not in the hierarchy at all.
So, I would revise your code so that the loadNibNamed: method gets run only once.
I am having a problem with my UITableView in iOS7. Initially, the data loads in just fine, and I get output in my console that proves that the cells are speaking to the data source correctly, but as soon as I tap anywhere on the table, the cells disappear and the table goes blank. The height of the cells in the empty UITableView seem to be honoring the height my custom prototype cell (410px), but all the data in the cells vanish, and the empty table view acts like it only has one cell in it (like its default state before it gets hooked up to the delegate).
I am using Storyboards for this app.
To get a little context, this app is similar to the iphone Instagram app, and I am using this application as way to learn iOS 7 development. I have been banging my head up against a wall trying to solve this issue, and I can't find any online resources that can help me solve this, so I wanted to ask all the smart peeps on Stack Overflow.
I have prepared a graphic that helps you see the problem
higher resolution version here
Here is my TableViewController code:
#interface PA_PhotoTableViewController ()
#property (nonatomic, copy) NSArray *photos;
#end
#implementation PA_PhotoTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.photos = [[PA_PhotoStore sharedPhotoStore] allPhotos];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[[PA_PhotoStore sharedPhotoStore] allPhotos] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
PA_PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PhotoCell" forIndexPath:indexPath];
PA_Photo *photo = (self.photos)[indexPath.row];
cell.photoTitle.text = photo.title;
cell.photoOwnerName.text = [NSString stringWithFormat:#"%#", photo.owner];
cell.photoLikes.text = #"99";
// Photo Image URL
NSURL *photoImageURL = [NSURL URLWithString:photo.image_full_url];
[cell.photoImage sd_setImageWithURL:photoImageURL placeholderImage:[UIImage imageNamed:#"lightGraySpinningLoader.gif"]];
// Photo Owner Image
[cell.photoOwnerImage sd_setImageWithURL:photoImageURL placeholderImage:[UIImage imageNamed:#"lightGraySpinningLoader.gif"]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// This code never gets called when I try to tap on a cell
NSLog(#"A row was selected");
}
- (void)dealloc {
NSLog(#"dealloc called in PA_PhotoTableViewController");
}
and here is the custom cell code PA_PhotoCell (consolidated .h & .m files):
#interface PA_PhotoCell : UITableViewCell
#property (nonatomic, weak) IBOutlet UIImageView *photoImage;
#property (nonatomic, weak) IBOutlet UILabel *photoTitle;
#property (nonatomic, weak) IBOutlet UILabel *photoOwnerName;
#property (nonatomic, weak) IBOutlet UIImageView *photoOwnerImage;
#property (nonatomic, weak) IBOutlet UILabel *photoLikes;
#property (nonatomic, weak) IBOutlet UILabel *photoTimestamp;
#property (nonatomic, weak) IBOutlet UILabel *photoComments;
#end
#implementation PA_PhotoCell
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
NSLog(#"in set selected");
}
-(void)setHighlighted:(BOOL)highlighted {
NSLog(#"in set highlighted");
}
You can see a few NSLog() calls to help me see if anything is getting called.
Where am I going wrong? The end goal is to click on one of the TableViewCell instances and launch a UINavigationController, I know how to do that, but I can't move on to that step until I figure out why my UITableView won't scroll, and why it disappears when I click on it!
EDIT: After much testing, debugging and experimentation, I have been able to conclude that the problem is actually not with the UITableView at all, and it is, in fact, a problem with how the UITableView is being loaded into its parent view. I still haven't found a solution to my problem, but I am getting closer to finding the cause. Here is what I have discovered:
First, when any of the UIButtons at the bottom of the screen are tapped (see photo reference), it loads the relevant instance of UIViewController into a UIView called placeholderView. When I run my problematic UITableView OUTSIDE of this UIView (where the UITableViewController is acting on its own, not embedded within another UIView) then the table works perfectly, it scrolls, it revives click events, and so on. So as soon as I load the UITableView into the UIView, the UITableView becomes unresponsive (it doesn't scroll or receive tap events) and any attempt to interact with it, the UITableView goes completely blank. My debugging session concludes that the NSArray *photos never gets reset to nil, or manipulated in anyway, the table just goes blank.
So does anyone have any ideas on what would cause a UITableView to do this when being loaded into a generic UIView? All the other views that get loaded into this generic UIView are responsive, and behave as expected. Its just this UITableView that is giving me problems.
If you review the graphic I attached to this post (above), you will see that I am using what appears to be a UITabBarView, but it is, in fact, just a generic view with UIButtons inside. The reason I decided to craft my own "UITabBarView look-alike" instead of using the ready-made UITAbBarView class was because I wanted to give custom functionality to the "menu" button on the bottom left (I want a nice UIView to slide in from the left, and stop about 60 pixels from the right of the screen when the "menu" button is tapped, and I can't figure out how to customize the behavior of the UITabBarView, so I opted for this approach.
Here is the code that is actually loading the UITableViewController into the subview (via a CustomStoryboardSegway):
// PA_HomeViewCustomStoryboardSegue.m
#import "PA_HomeViewCustomStoryboardSegue.h"
#import "PA_HomeViewController.h"
#implementation PA_HomeViewCustomStoryboardSegue
// to create a custom segue, you have to override the perform method
-(void)perform {
// get the source and destination view controllers
PA_HomeViewController *segueSourceController = (PA_HomeViewController *)[self sourceViewController];
UIViewController *destinationController = (UIViewController *)[self destinationViewController];
for (UIView *view in segueSourceController.placeholderView.subviews){
[view removeFromSuperview];
}
segueSourceController.currentViewController = destinationController;
[segueSourceController.placeholderView addSubview:destinationController.view];
}
#end
and here is the header file for my PA_HomeViewController (the view the contains the "placeholderView" which is the target view that loads the various UIViewControllers after the user has tapped the UIButtons at the bottom of the view (similar to a TabBarView) :
#interface PA_HomeViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIView *placeholderView;
#property (weak, nonatomic) UIViewController *currentViewController;
#end
I am hoping that I am just missing something obvious in the way that I am loading the UITableView into the placeholderView, and something in there is causing the UITableView to go completely blank.
When you display the UITableView in a different view, you must always make sure that the view controller which "hosts" the UITableView has a strong reference to its controller. In your case, the data source for the UITableView seems to be deallocated after adding the UITableView as subview.
Changing the currentViewController property from weak to strong should fix your problem.
In swift you need to declare viewcontroller object globally that would result in Strong, in case if you declare locally it results in keep disappearing the cells.
e.g.
var refineViewController : RefineViewController?
then you can access that controller using below code that would not result in disappearing cells.
func showRefineView(isFindHomeTab isFindHomeTab : Bool){
refineViewController = RefineViewController(nibName: String(BaseGroupedTableVC),bundle : nil)
refineViewController!.refineSearchDelegate = self
refineViewController!.view.frame = CGRectMake(0, -490, self.view.frame.size.width, self.view.frame.size.height)
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveEaseOut, animations:
{
self.refineViewController!.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)
self.refineViewController!.isFindHomeTab = isFindHomeTab
}, completion: nil)
self.view.addSubview(refineViewController!.view)
}
I experienced the exact same problem. The issue was that I was using a custom datasource class (called tableVCDataSource), and was setting the tableView's dataSource incorrectly in the ViewController class. I was doing:
override func viewDidLoad() {
mainTableView.dataSource = TableVCDataSource()
}
when I should have been doing:
fileprivate var tableVCDataSource: TableVCDataSource?
override func viewDidLoad() {
tableVCDataSource = TableVCDataSource()
mainTableView.dataSource = tableVCDataSource
}
This solved my issue.
I'm trying to write a shortcut for initializing my custom UIView's properties, rather than write out each one line by line, but unfortunately it's not working the way I expected.
// .h file
#property (nonatomic, strong) UIView *view1;
#property (nonatomic, strong) UIView *view2;
#property (nonatomic, strong) UIView *view3;
#property (nonatomic, strong) UIView *view4;
// .m file
UIView *views[] = { self.view1, self.view2, self.view3, self.view4 };
for ( int i = 0; i < sizeof(views) / sizeof(views[0]); i++ ) {
views[i] = [[UIView alloc] initWithFrame:self.frame];
}
NSLog( #"%#", self.view1 ); // prints null
Is this just not allowed with C-style arrays?
Your initialization of views[] looks fine, but as soon as you do
views[i] = ...
you are merely overwriting the contents of the array. You are, however, not initializing self.view1, self.view2, etc. Try
NSLog(#"%#", views[0]);
to see what I mean.
I would not do it like follows, but you could probably do (untested):
UIView **views[] = { &self.view1, &self.view2, &self.view3, &self.view4 };
for (int i = 0; i < (sizeof(views) / sizeof(views[0]); ++i)
{
*views[i] = [[UIView alloc] initWithFrame: self.frame];
}
NSLog(#"%#", self.view1);
I would rather initialize them one by one, without the array. I don't see any advantage in using an array and a loop.
You are just changing the pointer inside the array, the self.view1 pointer will still point to nil.
You could have an array of views as property:
#property(nonatomic,strong) UIView **views;
Or maybe you can use Key Value Coding like
for(int i=1;i<5;i++){
NSString* key = [NSString stringWithFormat:#"view%i",i];
UIView *view = //initialize view
[self setValue:view forKey:key];
}
Calling e.g. self.view1 at the beginning will give you nil before you have initialized it. So the line
UIView *views[] = { self.view1, self.view2, self.view3, self.view4 };
is effectively creating an array of 4 nils.
Inserting the new instances of UIView to the array afterwards has no effect on the properties. In fact, a property is only a pair of selectors (e.g. view1 and setView1:) and by itself cannot be used reliably with C-style pointers.
You could use the backing ivars with an array of UIView ** pointers, but you're most likely better off rethinking the whole approach.
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.
I'm getting a bit confused with UIViews recently, I have been using them fine up until this point and they are just refusing to be compliant!
I have a UIViewController, and this contains 5 different views. I have created IBOutlets for these views as I am wanting to swap them at runtime:
IBOutlet UIView *view1;
IBOutlet UIView *view2;
IBOutlet UIView *view3;
IBOutlet UIView *view4;
IBOutlet UIView *view5;
To make them easier to maintain I decided to keep them all within an array, called viewArray. Now I am trying to add the views to the array as follows:
viewArray = [NSArray arrayWithObjects:view1, view2, view3, view4, nil];
This is being called in the init function of my UIViewController class. I have linked up all the IBOutlets to their relevant views in the xib / interface file, but they do not appear to be initialized. Upon further debugging it looks like the views aren't initialized until after the init function is called?
So how can I create an array of these objects? I will need to select the relevant view before the view itself is shown, therefore viewDidLoad is not an option.
I know that you can grab the tags of things and implicitly set them using:
imageExample = (UIImageView *)[self.view viewWithTag:100];
But can this be used to find views, as surely it will be searching for the tags within the originally initialized view (view1)?
Thanks for any help in advanced,
Kind Regards,
Elliott
You can initialize the viewArray lazily, for the price of having to use self.viewArray instead of unqualified viewArray.
Here is how you can do it:
In the .h file:
NSArray* _viewArray;
#property (nonatomic, readonly) NSArray *viewArray;
In the .m file:
-(NSArray*) viewArray {
if (!_viewArray) {
_viewArray = [NSArray arrayWithObjects:view1, view2, view3, view4, nil];
}
return _viewArray;
}