Is there a way to set all UILabels as hidden in Objective-C? I'm showing and hiding labels based on if statements and feel like I'm writing really bulky code. Is there a way to select all UILabels to setHidden:YES a la CSS?
Edit: I need one of them visible at a time, not all hidden at once.
Thanks!
If all your labels lay at the same view you can use it's subviews property:
for (UIView *subview in self.view.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
subview.hidden = YES;
}
}
And if there are numerous of views with labels you can even add a category to the whole UIView.
#interface UIView (HideLabels)
- (void)hideAllLabels:(BOOL)hide withExcludedLabel:(UILabel *)label;
#end
#implementation UIView (HideLabels)
- (void)hideAllLabels:(BOOL)hide withExcludedLabel:(UILabel *)label
{
for (UIView *subview in self.view.subviews) {
if (subview != label && [subview isKindOfClass:[UILabel class]]) {
subview.hidden = YES;
}
}
}
#end
There's no other way to do this.
Edit: the code above updated according to your needs.
If you only need 1 UILabel at all time, you can reuse the same UILabel. The advantage is you use a bit less memory and you don't need to manage all the UILabels. The disadvantage is that you need to recalculate/store the coordinates to put the UILabel and stores the content of the UILabel (the management is shifted to this).
Now that the requirement has changed, the below answer is no longer valid. However, I still keep it there, in case anyone wants to hide/show all labels.
I don't think you can do it like CSS, but we can use a trick to avoid having to loop through all the UILabels to setHidden.
You can put all the UILabels as subview of a transparent UIView. The size and origin of the transparent UIView should be configured so that the coordinate is the same as when you don't use the transparent view (to avoid confusion). When you want to hide all UILabels, you can just hide the whole transparent UIView.
This has a drawback is that all the UILabels must be on top or under the existing view. This means that you cannot freely adjust some label to be on top of certain element, and some label to be below certain element on the existing view. You need to create another view for that purpose, and things will get quite messy there.
Related
Here's how the scroll views work: One scroll view is paging enabled in the horizontal direction. Each 'page' of this scroll view contains a vertically scrolling UITableView. Without modification, this works OK, but not perfectly.
The behaviour that's not right: When the user scrolls up and down on the table view, but then wants to flick over to the next page quickly, the horizontal flick/swipe will not work initially - it will not work until the table view is stationary (even if the swipe is very clearly horizontal).
How it should work: If the swipe is clearly horizontal, I'd like the page to change even if the table view is still scrolling/bouncing, as this is what the user will expect too.
How can I change this behaviour - what's the easiest or best way?
NOTE For various reasons, a UIPageViewController as stated in some answers will not work. How can I do this with cross directional UIScrollViews (/one is a table view, but you get the idea)? I've been banging my head against a wall for hours - if you think you can do this then I'll more than happily award a bounty.
According to my understanding of the question, it is only while the tableView is scrolling we want to change the default behaviour. All the other behaviour will be the same.
SubClass UITableView. UITableViews are subClass of UIScrollViews. On the UITableView subClass implement one UIScrollView's UIGestureRecognizer's delegate method
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer
{
//Edit 1
//return self.isDecelerating;
//return self.isDecelerating | self.bounces; //If we want to simultaneous gesture on bounce and scrolling
//Edit 2
return self.isDecelerating || self.contentOffset.y < 0 || self.contentOffset.y > MAX(0, self.contentSize.height - self.bounds.size.height); // #Jordan edited - we don't need to always enable simultaneous gesture for bounce enabled tableViews
}
As we only want to change the default gesture behaviour while the tableView is decelerating.
Now change all 'UITableView's class to your newly created tableViewSubClass and run the project, swipe should work while tableView is scrolling. :]
But the swipe looks a little too sensitive while tableView is scrolling. Let's make the swipe a little restrictive.
SubClass UIScrollView. On the UIScrollView subclass implement another UIGestureRecognizer's delegate method gestureRecognizerShouldBegin:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
if (abs(velocity.y) * 2 < abs(velocity.x)) {
return YES;
}
}
return NO;
}
We want to make the "swipe is clearly horizontal". Above code only permits gesture begin if the gesture velocity on x axis is double than on y axis. [Feel free to increase the hard coded value "2" if your like. The higher the value the swipe needs to be more horizontal.]
Now change the `UiScrollView' class (which has multiple TableViews) to your ScrollViewSubClass. Run the project. :]
I've made a project on gitHub https://github.com/rishi420/SwipeWhileScroll
Although apple doesn't like this method too much:
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behavior can result
because touch events for the two objects can be mixed up and wrongly
handled.
I've found a great way to accomplish this.
This is a complete solution for the problem. In order to scroll the UIScrollView while your UITableView is scrolling you'll need to disable the interaction you have it.
- (void)viewDidLoad
{
[super viewDidLoad];
_myScrollView.contentSize = CGSizeMake(2000, 0);
data = [[NSMutableArray alloc]init];
for(int i=0;i<30;i++)
{
[data addObject:[NSString stringWithFormat:#"%d",i]];
}
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self.view addGestureRecognizer:tap];
}
- (void)handleTap:(UITapGestureRecognizer *)recognizer
{
[_myTableView setContentOffset:_myTableView.contentOffset animated:NO];
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
scrollView.userInteractionEnabled = YES;
}
To sum up the code above, if the UITableView is scrolling, set userInteractionEnabled to NO so the UIScrollView will detect the swipe. If the UITableView is scrolling and the user taps on the screen, userInteractionEnabled will be set to YES.
Instead of using UIScrollView as a container for these multiple table views, try using a UIPageViewController.
You can even integrate this into your existing view controller setup as a child view controller (directly replacing the UIScrollView).
In addition, you'll likely want to implement the required methods from UIPageViewControllerDataSource and possibly one or more of the methods from UIPageViewControllerDelegate.
Did you try the methods : directionalLockEnabled of both your table and scroll and set them up to horizontal for one and vertical for the other ?
Edit :
1)
What you want to do is very complicate since the touch wait some time (like 0.1s) to know what your movement will be. And if your table is moving, it will take your touch immediately whatever it is (because it's suppose to be reactive movement on it).
I don't see any other solution for you but to override touch movement from scratch to detect immediately the kind of mouvement you want (like if the movement will be horizontal) but it will be more than hard to do it good.
2)
Another solution I can advise you is to make your table have left and right margin, where you can touch the parent scroll (pages thing so) and then even if your table is scrolling, if you touch here, only your paging scroll will be touched. It's simpler, but could not fit with your design maybe...
Use UIPageViewController and in the -viewDidLoad method (or any other method what best suits your needs or design) get UIPageViewController's UIScrollView subview and assign a delegate to it. Keep in mind that, its delegate property won't be nil. So optionally, you can assign it to another reference, and then assign your object, which conforms to UIScrollViewDelegate, to it. For example:
id<UIScrollViewDelegate> originalPageScrollViewDelegate = ((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]).delegate;
[((UIScrollView *)[pageViewController.view.subviews objectAtIndex:0]) setDelegate:self];
So that you can implement UIScrollViewDelegate methods with ease. And your UIPageViewController will call your delegate's -scrollViewDidScroll: method.
By the way, you may be obliged to keep original delegate, and respond to delegate methods with that object. You can see an example implementation in ViewPagerController class on my UI control project here
I faced the same thing recently. My UIScrollview was on paging mode and every page contained a UITableView and like you described it worked but not as you'd expected it to work. This is how solved it.
First I disabled the scrolling of the UIScrollview
Then I added a UISwipeGestureRecognizer to the actual UITableView for left and right swipes.
The action for those swipes were:
[scroll setContentOffset:CGPointMake(currentPointX + 320, PointY) animated:YES];
//Or
[scroll setContentOffset:CGPointMake(currentPointX - 320 , PointY) animated:YES];
This works flawlessly, the only down side is that if the user drags his finger on the UITableVIew that will be considered as a swipe. He won't be able to see half of screen A and half of screen B on the same screen.
You could subclass your scroll view and your table views, and add this gesture recognizer delegate method to each of them...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
I can't be sure this is exactly what you are after, but it may come close.
I created a simple window with the purpose of being something like a "Wizard" (I know Apple guidelines basically forbid you to, I tried to convince the customer, but whatever.
It is just a simple view with two Custom Views inside, one in the bottom part which contains a "previous" and "next" button, and a bigger view at the top which takes most of the space.
I called the bottom view "NavigationView" and the top one "ContainerView".
I created an array to hold a series of views the user is supposed to navigate through with the "next" and "previous" buttons.
So, here's my code.
- (IBAction) next:(id)sender{
currentViewIndex++;
[self animatePushView:YES];
}
- (IBAction)previous:(id)sender{
currentViewIndex--;
[self animatePushView:NO];
}
- (void) animatePushView:(BOOL)forward{
NSView *nextView = [viewCollection objectAtIndex:currentViewIndex];
for (NSView *subView in [containerView subviews]) {
[subView removeFromSuperview];
}
[containerView addSubview:nextView];
[nextView setFrame:containerView.bounds];
[containerView setNeedsDisplay:YES];
}
It's pretty straightforward I think. I have an array which contains the next view to be displayed.
What happens is that I find the next view centered in the lower left part of the ContainerView. Why does this happen?
Also, as you may have guessed, I'm a newbie at managing views, even though I've been working on objective-c for quite some time, so if there's some best practice I'm missing I'm open to suggestions.
Thanks!
EDIT:
I forgot to add:
Some of these views have different sizes, and I would like to be able to change the window size according to the view size.
[nextView setFrame:containerView.bounds];
You are assigning container view bounds to the next view frame (doc).
What you probably want is assigning the current view frame to the next view frame, and possibly adjust width and height.
Keep a reference to the current displayed view, something like this (_currentView is an ivar of type NSView *) :
- (IBAction) next:(id)sender{
currentViewIndex++;
[self animatePushView:YES];
}
- (IBAction)previous:(id)sender{
currentViewIndex--;
[self animatePushView:NO];
}
- (void) animatePushView:(BOOL)forward{
NSView *nextView = [viewCollection objectAtIndex:currentViewIndex];
[nextView setFrame:_currentView.frame];
[_currentView removeFromSuperview]; // _currentView is retained in the collection
[containerView addSubview:nextView];
_currentView = nextView;
[containerView setNeedsDisplay:YES];
}
Ok, I figured it out.. Finally..
The problem was that the first view I had to show was already contained in the container view in the .xib file.
I don't really know why, but it probably caused some problem with the retain count of the container view, because it was released on the first click.
Releasing the container view would reposition the view on (0,0) probably because its frame was null, and the view would flash because it wasn't retained correctly.
Removing the view from the .xib file and adding it via code works properly anyway.
I have a regular-style UITableView—the one that has a white background and gray horizontal lines to separate the rows.
I have another custom UIView that is just a 100x100 rectangle filled with redColor.
How can I put the latter into the former, such that it appears over the horizontal lines, but is still a “part” of the table view in the sense that when I scroll the table view around, the red view scrolls with it? In fact, I should also be able to put my finger on the red area and scroll the table view.
Once again, if the red view is placed to overlap some horizontal lines, it should appear over the lines. Sadly, when I just add the red view as a subview to the table view, the horizontal lines go over the red view; see this screenshot.
How can this be accomplished?
The correct place to handle the stacking order of your red square is in the layoutSubviews method. The table view sends itself layoutSubviews any time it adds or removes subviews (and at other times).
You need to make a subclass of UITableView that has a reference to the red square:
#interface MyTableView : UITableView
#property (weak, readonly) IBOutlet UIView *redSquare;
#end
You can initialize redSquare in whatever way you want. I just put it in my nib along with the table view, and moved it to be a subview of the table view in awakeFromNib:
#implementation MyTableView
#synthesize redSquare = _redSquare;
- (void)awakeFromNib {
[self addSubview:self.redSquare];
}
Anyway, to actually make sure the red square is always on top of the table cells and grid lines, override layoutSubviews like this:
- (void)layoutSubviews {
[super layoutSubviews];
[self bringSubviewToFront:self.redSquare];
}
EDIT
If you are trying to add the view a above the lines (hide the lines) try to use – bringSubviewToFront: to take it to the front of the table view.
[self.tableView bringSubviewToFront:redView];
ELSE
Add the view to the self.tableView.tableHeaderView this will place it above the table view and will scroll with the table view.
UIView *redView = [[UIView alloc]init];
redView.frame = //set the frame
redView.backgroundColor = [UIColor redColor];
self.tableView.tableHeaderView = redView;
Good Luck
Just add this view to UITableView as subview:
[tableView addSubview:myRedView];
See userInteractionEnabled property in order to handle interaction and scrolling.
Make your view a subview of any normal subview of the UITableView: a header, a footer or any UITableViewCell.
I think what you've described can best be achieved using a UITableViewCell or even a subview of the header. A cell has the inherent ability to scroll the table and can be customized any way you like it. It's essentially a view.
In your situation, for example, you may want the red box to appear by default at the top of the table. You would make the first cell a 'red box' cell, where you would insert your red box into the cell's content view.
So, your problem is basically, that the UITableViewCells of a UITableView are added as Subviews dynamically, and you cannot control wether they are added in front of or behind your view.
So to keep your view at the front, you need to get it back there every time cells may be added, which occurs when the UITableView scrolls.
I would suggest you try adding your custom view as a subview and then override -scrollViewDidScroll like so:
- (void)scrollViewDidScroll {
[super scrollViewDidScroll];
// myCustomView is your custom view referenced in an IVar
[self.tableView bringSubviewToFront:myCustomView];
}
Edit your viewWillAppear delegate with these lines
UIView *redView=[[UIView alloc]initWithFrame:CGRectMake(30,30, 100, 100)];
redView.backgroundColor=[UIColor redColor];
[self.tableView addSubview:redView];
I was having trouble getting a UIScrollView with multiple UIImageView subviews to zoom properly so I tried using a UIScrollView with multiple UIScrollView subviews inside of it and a UIImageView inside of each of those. That way one UIScrollView can only scroll and the other can only zoom.
My question is: How can I access subviews of a subview. I know that normally I can access subviews using [scrollView viewWithTag:tagInt]; for example, but I can't seem to access the subviews of a subview using [[scrollView viewWithTag:tagInt] viewWithTag:tagInt2]; since viewWithTag only returns a single UIView and not all of it's subviews.
I could always give each subview a unique tag and access them that way, but that doesn't seem to be the most elegant solution.
What is the best way to access a subView and then get to the subView's subview (ie: access my UIScrollView used for zooming which is a subview of my main view, and then access it's subView UIImageView)?
If you don't feel comfortable subclassing for now, you'd have to assign tags to the UIScrollViews containing images. You'd then have to do what the BobC suggested with a little more work.
for (UIView *subview in [myScrollView subviews])
{
//check if the current subview is one of the UIScrollViews
if (subview.tag > 100)
//do something with the UIScrollView
}
The most elegant way to do this would be to subclass UIScrollView, give it a UIImageView property. That way, you could access this UIImageView with something like:
MyScrollView *myScrollView = (MyScrollView *)[scrollView viewWithTag:tagInt];
UIImageView *imageView = myScrollView.imageView;
Hope this helps!
You can get an array of subviews of any UIView using subviews property.
for(UIView *subview in [myScrollView subviews]) {
// do anything with your subview here.
}
i made a second uiview in mei .xib file. the first view is landscape and i get it by following code
ItemController *newItem;
newItem = [[ItemController alloc] init];
newItem.view.....
how can i "activate" the second view, so i can use it with
newItem.view2...
is that possible? the second view is portait mode, so it should be hidden and when turning the ipad the first view should be hidden and the second gets visible.
thanks
If I understand your question correctly, you can use the method willRotateToInterfaceOrientation:duration: in UIViewController.
You can declare two UIView variables and set connections to them using IB:
IBOutlet UIView *view1;
IBOutlet UIView *view2;
Then, in willRotateToInterfaceOrientation:duration: you can swap the views. Assuming that your ItemController is a subclass of UIViewController, you could have something like this:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
[self setView:view1];
}
else {
[self setView:view2];
}
}
When the user rotates the iPad this method will get called automatically.
NOTE: make sure you set shouldAutorotateToInterfaceOrientation: to return YES, and you may have to check the orientation to set the view property to the correct view initially.
EDIT: My answer is assuming that you have two views with distinct layouts/elements and not two views that are essentially the same thing sized for different orientations. If the latter is the case, there is probably a better way to do it using only one view.
The approach you are taking will lead you to a trouble situation when you have some IBActions to associate with click of buttons or change the label text (which is on xib) etc.
The best approach is to change the frame height width and positions when the orientation changes. Although this is difficult in beginning but it will save you from lots of troubles.
For assistance check Rotate UIViewController to counteract changes in UIInterfaceOrientation
Hope this helps.
Thanks,
Madhup