multiple UIScrollViews on a UIView - iphone

Is there any way to add multiple UIScrollView's on a single UIView?
I made the UIView 2 parts. On the first part of view, I want to add one UIScrollView, and on the other, I want to add a second scrollview. The problem I'm facing is when I'm trying to zoom on the first, the second scroll is also responding.
How can I avoid that?

You should differentiate your scroll view by its tag property. Like :
assign tag to your scroll views
scrollView1.tag = 2001;
scrollView2.tag = 3001;
And then
- (void)scrollViewDidZoom:(UIScrollView *)myScrollView
{
if (myScrollView.tag == 2001)
{
//do stuff with scrollView1
}
else if (myScrollView.tag == 3001)
{
//do stuff with scrollView2
}
}

Use this Delegate method:-
Delegate methods send with it the object that sent the message (the UIScrollView in this case). So, all you have to do is check that against your instance variables of scrollView1 and scrollView2.
- (void)scrollViewDidZoom:(UIScrollView *)myScrollView {
if (myScrollView == scrollView1) {
//do stuff with scrollView1
} else if (myScrollView == scrollView2) {
//do stuff with scrollView2
}
}

Related

Swallowing a ccTouch on ccTouchMoved

I am attempting to let players drag objects in my game from one part of the screen to another. The problem is, the objects which are to be dragged have layers beneath them that need to receive touches, too. Normally I'd just swallow the touch, but as far as I can tell, that can only be done during ccTouchBegan. I can't tell if the user is attempting to drag an object until after ccTouchMoved is called, so I need a way to explicitly swallow (or otherwise prevent lower layers) from receiving the touch after I've determined that it is a touch I'm interested in (within ccTouchMoved).
I got almost the same problem, but I don't know if my solution would fit here. The main idea was that objects which should be dragged were children on the same CCNode hierarchy with beneath items. The solution consists in the fact that parent disabled children's touch events, then intercepts these events. In case some object was dragged parent sends all event to it, in the other case parent handles the event itself.
Let me try to show what I mean. Create protocol for items which can swallow touches ccTouchMoved:
#protocol SwallowingOnTouchMovedNode
{
-(BOOL) ccTouchMoved:(UITouch*)touch; // not full signature for simpleness (UIEvent should be also here)
}
Then create layer, which will manually handle the touches of its children:
#interface TouchHandler : CCLayer
{
NSMutableArray *draggableChildren_, nonDraggableChildren_, *claimedChildren_;
BOOL isDragging_;
}
#implementation TouchHandler
-(id) init
{
...
self.isTouchEnabled = YES;
draggableChildren_ = [[NSMutableArray alloc] init];
nonDraggableChildren_ = [[NSMutableArray alloc] init];
claimedChildren = [[NSMutableArray alloc] init];
...
}
Create two methods for TouchHandler for adding two types of children - the ones which can be dragged and the others. That methods will disable touches on children so the parent will manually handle them.
-(void) addChild:(CCNode*)child shouldBeDragged:(BOOL)shouldBeDragged
{
NSMutableArray *arrayToAddChild = shouldBeDragged ? draggableChildren_ : nonDraggableChildren_;
[arrayToAddChild addObject:child];
// note, that if the child has its own children, you will need to
// set isTouchEnabled on all of them, as adding all children to array recursively
if ([child respondsToSelector:#selector(setIsTouchEnabled:)]) ((CCLayer*)child).isTouchEnabled = NO;
[self addChild:child]; // maybe you need to call -addChild:zOrder:tag: here
}
Then override touch handles like that:
-(BOOL) ccTouchBegan:(UITouch*)touch
{
for (CCNode *child in draggableChildren)
{
if ([child ccTouchBegin:touch])
{
// this behavior looks like the one in CCTouchDispatcher -
// we claim children which return YES for touchBegin
[claimedChildren addObject:child];
}
}
}
-(void) ccTouchMoved:(UITouch*)touch
{
for (CCNode *child in claimedChildren)
{
if ([(id<SwallowingOnTouchMovedNode>)child ccTouchMoved:touch])
{
isDragging_ = YES;
}
}
// if no one swallowed touches
if (!isDragging_)
{
for (CCNode *child in nonDraggableChildren)
{
// we did not called ccTouchBegan earlier for these items,
// so for consistency we need to call it now
if ([child ccTouchBegin:touch])
{
[child ccTouchMoved:touch];
[claimedChildren addObject:child];
}
}
}
}
-(void) ccTouchEnded:(UITouch*)touch
{
isDragging_ = NO;
for (CCNode *child in claimedChildren)
{
[child ccTouchEnded];
}
}
Do not forget to implement -ccTouchCancelled. This code is pretty concrete, so you may need to make some changes, but I hope I have explained my idea. In general, the TouchHandler may not even be the CCLayer to work like this, just add it as targeted delegate for receiving touches.
There is another way, which seems to be more consistent and correct from OOP point of view, but I am not sure about it. The behavior in ccTouchBegin, ccTouchMoved and ccTouchEnded almost duplicates the one in the CCTouchDispatcher. You can subclass it and override some methods for receiving touch events and implement -(BOOL)ccTouchMoved, as I've done. Also, I don't know if we can replace default CCTouchDispatcher. Hope this will help!

How to check if a UIView is a subview of a parent view

I have an app which I add a subview to (and remove the same subview based on user interactions). I am looking for a way to check whether the subview is present or not at any given time.
For example:
In the current view (UIView *viewA) I add a subview (UIView *viewB). I then want a way of checking whether viewB is being displayed at any given time.
Sorry if this isn't very clear, it's quite hard to describe.
an UIView stores its superview and is accessible with the superview-property just try
if([viewB superview]!=nil)
NSLog(#"visible");
else
NSLog(#"not visible");
But the better approach is to use the hidden-property of UIView
I went through the same issue and consulted Apple Documentation and came up with this elegant solution:
if([self.childView isDescendantOfView:self.parentView])
{
// The childView is contained in the parentView.
}
I updated to Swift4, Thanks a lot to #Shinnyx and #thomas.
if viewB.superview != nil{
print("visible")
}
else{
print("not visible")
}
if selfView.isDescendant(of: self.parentView) {
print("visible")
}
else{
print("not visible")
}
func isDescendant(of: UIView)
Returns a Boolean value indicating whether the receiver is a subview of a given view or identical to that view.
Here's a method I put in the appDelegate so that I can display the entire subview hierarchy from any point.
// useful debugging method - send it a view and it will log all subviews
// can be called from the debugger
- (void) viewAllSubviews:(UIView *) topView Indent:(NSString *) indent {
for (UIView * theView in [topView subviews]){
NSLog(#"%#%#", indent, theView);
if ([theView subviews] != nil)
[self viewAllSubviews:theView Indent: [NSString stringWithFormat:#"%# ",indent]];
}
}
call it with a string with one character and it will indent for you. (i.e. [appDelegate viewAllSubviews:self.view Indent:#" "];)
I find it useful to clear the debug pane first, then call this from the debugger, and copy it into a text editor like BBEdit that will show the indents.
You can call it using the mainWindow's view and see everything on your screen.

How to tell programmatically if a IBAction has been called by code or by user action

How can I tell programmatically if a IBAction has been called by code or by user action.
Eg
I have a method, -(IBAction)someMethodWithIts:(id)sender
which I have linked to a valueChanged on a UISegmentedControl.
It can be called by,
User changing segment
setting the selectedIndex in code
calling [self someMethodWithIts:foo];
Is there a way to distinguish if the call has come from the first way?
Cheers
Sam
If you can pass nil as the sender (which is traditional) and use that to indicate it was sent programmatically, that's ok. But anything else I believe is too fragile and you should break up the code like this:
- (void)someMethod {
// stuff shared by everyone
}
- (IBAction)someMethodWithIts:(id)sender {
// stuff specific to IBAction
[self someMethod];
}
If you really want a sender, then you can do it this way:
- (void)someMethodWithIts:(id)sender triggeredByUser:(BOOL)isUser {
}
- (IBAction)someMethodWithIts:(id)sender {
[self someMethodWithIts:sender triggeredByUser:YES];
}
But in general, if you want the IBAction to be different than programatic changes, then don't wire programatic changes to the IBAction.
if ([sender isKindOfClass:[UIControl class]]) {
UIControl *controlSender = (UIControl *)sender;
if (sender.selected) {
// then it's #1
} else {
// then it's #2
}
} else if ([sender isKindOfClass:[Foo class]]) {
// then it's #3
}
Might work. Or poke around other properties of sender defined on UIControl or UISegmentedControl. Maybe state. My guess is you can find something that's different when the user is interacting vs. when they're not.
You really want to use method like this
-(IBAction)actionWithSender:(id)sender event:(UIEvent*)event
{
if (event) {
} else {
}
}
If you find the event parameter is nil, it's from a call in your code, otherwise, the call is from user event.

iPhone - how to scroll two UITableViews symmetrically

In my app, I have two tableViews side by side. When the user scrolls on, I would like the second one to scroll at the same time, so it looks almost like one table with two different columns. I'm a bit lost on how to go about doing this, any suggestions?
Thanks,
Greg
Conveniently, UITableView is a subclass of UIScrollView. There exists a UIScrollViewDelegate, which has this method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
If you implement that method, you can get the contentOffset property of the scrollView argument. Then, you should use
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
and set the new content offset. So something like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
UIScrollView *otherScrollView = (scrollView == self.tableView1) ? self.tableView2 : self.tableView1;
[otherScrollView setContentOffset:[scrollView contentOffset] animated:NO];
}
You can cast to a UITableView if you'd like, but there's no particular reason to do so.
You'll want to look into the UIScrollViewDelegate - say you've got two scroll views, A and B.
Use the scrollViewDidScroll delegate method of scroll view A to get the offset, and then in the same method call setContentOffset on scroll view B, passing in the value you get from the delegate.
It actually shouldn't be more than 2-3 lines of code once you've set-up your delegate methods.
also, the tableview that got scrolled by the user should not be sent setContentOffset: message in scrollViewDidScroll, since it will get the app into endless cycle. so additional UIScrollViewDelegate methods should be implemented in order to solve the problem:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
beingScrolled_ = nil;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if(beingScrolled_ == nil)
beingScrolled_ = scrollView;
}
and modifying Inspire48's version scrollViewDidScroll: accordingly:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
UIScrollView *otherScrollView = (scrollView == self.tableView1) ? self.tableView2 : self.tableView1;
if(otherScrollView != beingScrolled)
{
[otherScrollView setContentOffset:[scrollView contentOffset] animated:NO];
}
}
where beingScrolled_ is an ivar of type UIScrollView
UITableView is a subclass of UIScrollView. Swift solution:
extension yourViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let otherScrollView = (scrollView == tableViewLeft) ? tableViewRight : tableViewLeft
otherScrollView?.setContentOffset(scrollView.contentOffset, animated: false)
}
}
And stop the bounce of both the tables
tableViewLeft.bounces = false
tableViewRight.bounces = false
If you want to make vertical scroll Indicators go, write the below code
tableViewLeft.showsVerticalScrollIndicator = false
tableViewRight.showsVerticalScrollIndicator = false
in swift scroll two uitableviews symmetrically:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if tb_time1 == scrollView {
tb_time2.contentOffset = tb_time1.contentOffset
}else if tb_time2 == scrollView {
tb_time1.contentOffset = tb_time2.contentOffset
}
}

UIView: How to find out if a view is already exists?

I was wondering how to find out if a subview (in my case pageShadowView) has already been added to my view.
I've come up with this, but it doesn't really work:
if ([pageShadowView isKindOfClass:[self.view class]]) {
[self.view addSubview:pageShadowView];
}
Also, I'm still confused about the self.-thing. I know that this has to do with making clear that we are talking about the view of the current ViewController ... but do I really need it if there (1) are no other ViewControllers or (2) if it doesn't really matter because if I ever wanted to refer to another viewController, I'd make sure to call it?
I'm sorry if this is all very basic, but I'd be very grateful for your comments.
Here:
BOOL doesContain = [self.view.subviews containsObject:pageShadowView];
And yes, you need this self. There is no explicit ivar "view" on UIViewController. The self.view statement is actually a call on method [self view] which is a getter for UIViewController's view.
Give it a unique tag: view.tag = UNIQUE_TAG, then check the container view for existence:
BOOL alreadyAdded = [containerView viewWithTag:UNIQUE_TAG] != nil;
you can find a sub view like this
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UIView class]])
{
//here do your work
}
}
There's one more way to find, in Swift: isDescendant(of view: UIView) -> Bool or in Obj-C: - (BOOL)isDescendantOfView:(UIView *)view
Swift:
if myView.isDescendant(of: self.view) {
//myView is subview of self.view, remove it.
myView.removeFromSuperview()
} else {
//myView is not subview of self.view, add it.
self.view.addSubview(myView)
}
Obj-C:
if([myView isDescendantOfView:self.view]) {
//myView is subview of self.view, remove it.
[myView removeFromSuperView];
} else {
//myView is not subview of self.view, add it.
[self.view addSubView:myView];
}
SWIFT VERSION:
let doesContain = self.view?.subviews.contains(pageShadowView)
best and easy way to find that your view is exist or not , there are many way like you check a view is contain or not in super view or just view some time this method is failed because if the Uiview is already remove then error occur,
so code is here :
here errorView is my UiView
errorView.tag = 333
if ( self.view?.viewWithTag(333) != nil ){
print("contain")
}
else {
print("not contain")
}
To add to what coneybeare said, you could do the following. If you set your object.tag=100;
if ([self.view.superview viewWithTag:100] == nil){ //if statement executes if the object with tag 100 in view.superview is absent (nil)
if ([self.view viewWithTag:100] == nil){ //if statement executes if the object with tag 100 in view (not superview) is absent (nil)
add a retain value of the view
then check the retain value
if > 1 , then exist , if perfect should be 2
then release it once