When does setter method get called from UIView subclass - objective-c

I am taking the free Stanford course on iTunesU(193P) and we created setting up a class that is a subclass of UIView and created a public property called scale. The idea was that when we pinch, the scale of the view is changed accordingly but I am confused about when the setter of the property scale gets called. here is the relevant code below:
#interface FaceView : UIView
#property (nonatomic) CGFloat scale; //anyone who wants do publicly can set my scale
-(void)pinch:(UIPinchGestureRecognizer *)gesture;
#end
#synthesize scale = _scale;
#define DEFAULT_SCALE 0.90
-(CGFloat)scale{
if(!_scale){
return DEFAULT_SCALE;
}else {
return _scale;
}
}
-(void)setScale:(CGFloat)scale{
NSLog(#"setting the scale");
if(scale != _scale){
_scale = scale;
[self setNeedsDisplay];
}
}
-(void)pinch:(UIPinchGestureRecognizer *)gesture{
if ( (gesture.state == UIGestureRecognizerStateChanged) || (gesture.state == UIGestureRecognizerStateEnded)){
self.scale *= gesture.scale;
gesture.scale = 1;
}
}
When I am in "pinch mode" the setScale method continues to be called as I am pinching as my NSLog statement prints out until I stop the pinch. When or how does the setScale method continued to be called when there isn't any code programmatically calling it? Perhaps I missed something along the way here.

#cspam, remember that to set the gesture recognizer is 2 steps:
1) Adding a gesture recognizer to UIView - This kind of confused me in the lecture but eventhough he is saying add a gesture recognizer to UIView, he really means add a gesture recognizer FOR UIView, IN UIViewController. That is the code that you are missing that you have to add in the UIViewController subclass in this case (the Faceviewcontroller - your name might be different) and that is what will keep calling your pinch method in FaceView above:
UIPinchGestureRecognizer *pinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:self.faceView action:#selector(pinch)];
[self.faceView addGestureRecognizer:pinchGesture];
You would add this code in your UIViewController (subclass) in the setter method of your UIView [in other words, create and connect an IBOutlet property in your UIViewController to your UIView in the storyboard] and override the setter method to include the code above.
2) The second part is what you have in your code. So pinch method will be called everytime the controller senses a pinch gesture.
Hope this helps.

Related

UIImageView shifts left before animating to the right

Newbie here,
With this code in the .m file I aimed to animate an UIImageView to the right
#interface ViewController ()
#property (strong, nonatomic) IBOutlet UIImageView *myImageView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.myImageView.center = CGPointMake(self.myImageView.center.x + 100, self.myImageView.center.y);
}
completion:^(BOOL finished){
}];
}
But what actually happens is that the image view shifts 100 points to the left and then animates to the right by 100 points back to its original position. I don't know why this happens. How can I achieve an animation that moves directly to the right?
Note: I'm not sure if this has to do with anything, but I added constraints in the interface builder to center this UIImageView in the middle.
Create IBOutlets for the constraints you want change. Just like you would for any other UIKit element. Then in your code instead of doing:
self.myImageView.center = CGPointMake(self.myImageView.center.x + 100, self.myImageView.center.y);
Do something like this:
self.imageViewTralingSpaceConstraint.constant = 100; //Or whatever value makes sense
Then in your animateWithDuration block add [self.view layoutSubViews];
you probably want to put this code in your viewDidAppear method rather than your viewDidLoad. this would make the animation happen after the view appears rather than before it. the viewDidLoad method is called after the view hierarchy is loaded into memory but not neccessarily after it is all laid out.
here is a link to the uiviewcontroller reference. https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/
check out these two methods there. if you continue doing iOS development you will get to know these methods really well.

Without subclassing a UIView or UIViewController: possible to catch if a subview was added?

Is there a way to catch an event or notification if a view was added as subview to an existing view of a controller? I have a library here and I cannot subclass but need to know if a specific subview was added to trigger a custom action.
Is there a chance?
I will try adding a category for the didAddSubview method.
EDIT
Category is an alternative to subclassing so you could use something along those lines:
.h:
#import <UIkit/UIKit.h>
#interface UIView (AddSubView)
- (void)didAddSubview:(UIView *)view
#end
.m:
#implementation UIView (AddSubView)
- (void)didAddSubview:(UIView *)view
{
[self addSubview: view];
// invoke the method you want to notify the addition of the subview
}
#end
Not that I think this method is cleaner than the one #tiguero suggested, but I think it's slightly safer (see why using categories could be dangerous in my comments to his answer) and offers you more flexibilty.
This is somehow, although not exactly, but more at the conceptual level, the same way KVO works. You basically, dynamically alter the implementation of willMoveToSuperview and add your notification code to it.
//Makes views announce their change of superviews
Method method = class_getInstanceMethod([UIView class], #selector(willMoveToSuperview:));
IMP originalImp = method_getImplementation(method);
void (^block)(id, UIView*) = ^(id _self, UIView* superview) {
[_self willChangeValueForKey:#"superview"];
originalImp(_self, #selector(willMoveToSuperview:), superview);
[_self didChangeValueForKey:#"superview"];
};
IMP newImp = imp_implementationWithBlock((__bridge void*)block);
method_setImplementation(method, newImp);

set placeholder for uitextview methods are not being called

I saw this answer of how to create a placeholder for UITextView.
I took the following steps:
Add to the .h class the declaration:
#interface AdjustPhotoViewController : UIViewController<UITextViewDelegate>
Added the method:
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
NSLog(#"%d",[textView tag]);
if ([textView tag]==1){
campaignTitle.text = #"";
}else{
campaignDescription.text = #"";
}
return YES;
}
But I don't see that the method is being invoked!
What am I missing?
textView is already delegated via the storyboard to the view
SOLVED:
The problem was that it wasn't delegated. Although I was using storyboard - it was only an outlet, not a delegate.
Remember that if you are using storyboard, you need to delegate also from the text view to the orange button of the view! not only the other way
What am I missing?
Actually setting the delegate.
textView.delegate = self;
Merely conforming to a protocol won't magically make your object into the delegate of an arbitrary object; that's just a formal thing, and anyways, how on Earth would the UITextField know which particular instance of the class it has to assign its delegate?

Topbar cannot rotate in landscape view with storyboard

I have just converted from .nib files to storyboard, but suddenly the view wont rotate topbar in landscape view. All the settings are "inferred" in my view, and i have not really made any changes since the conversion.
Is this a common problem when upgrading? I have not found any specific info.
And furthermore i do not force any view rotations in my code.
If any more info is needed i can supply anything!
Thanks in advance.
ViewController:
- (void) viewDidLoad {
[super viewDidLoad];
self.view.autoresizingMask = UIViewAutoresizingNone;
self.view.autoresizesSubviews = UIViewAutoresizingNone;
}
I've taken a look at your code and you seem to be missing a method that allows your view controller to rotate freely.
Subclass UIViewController e.g. like this:
// .h file
#interface OrientationAwareViewController : UIViewController
#end
// m.file
#implementation OrientationAwareViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
#end
Then set OrientationAwareViewController in the storyboard as your main view controller's class name. That said, I have no idea how this worked for you when using nibs :) Documentation says clearly:
By default, this method returns YES for the UIInterfaceOrientationPortrait orientation only. If your view controller supports additional orientations, override this method and return YES for all orientations it supports.

How can I tell a UIGestureRecognizer to cancel an existing touch?

I have a UIPanGestureRecognizer I am using to track an object (UIImageView) below a user's finger. I only care about motion on the X axis, and if the touch strays above or below the object's frame on the Y axis I want to end the touch.
I've got everything I need for determining if a touch is within the object's Y bounds, but I don't know how to cancel the touch event. Flipping the recognizer's cancelsTouchesInView property doesn't seem to do what I want.
Thanks!
This little trick works for me.
#implementation UIGestureRecognizer (Cancel)
- (void)cancel {
self.enabled = NO;
self.enabled = YES;
}
#end
From the UIGestureRecognizer #enabled documentation:
Disables a gesture recognizers so it
does not receive touches. The default
value is YES. If you change this
property to NO while a gesture
recognizer is currently recognizing a
gesture, the gesture recognizer
transitions to a cancelled state.
#matej's answer in Swift.
extension UIGestureRecognizer {
func cancel() {
isEnabled = false
isEnabled = true
}
}
Obj-C:
recognizer.enabled = NO;
recognizer.enabled = YES;
Swift 3:
recognizer.isEnabled = false
recognizer.isEnabled = true
How about this from the apple docs:
#property(nonatomic, getter=isEnabled) BOOL enabled
Disables a gesture recognizers so it does not receive touches. The default value is YES. If you change this property to NO while a gesture recognizer is currently recognizing a gesture, the gesture recognizer transitions to a cancelled state.
According to the documentation you can subclass you gesture recogniser:
In YourPanGestureRecognizer.m:
#import "YourPanGestureRecognizer.h"
#implementation YourPanGestureRecognizer
- (void) cancelGesture {
self.state=UIGestureRecognizerStateCancelled;
}
#end
In YourPanGestureRecognizer.h:
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface NPPanGestureRecognizer: UIPanGestureRecognizer
- (void) cancelGesture;
#end
Now you can call if from anywhere
YourPanGestureRecognizer *panRecognizer = [[YourPanGestureRecognizer alloc] initWithTarget:self action:#selector(panMoved:)];
[self.view addGestureRecognizer:panRecognizer];
[...]
-(void) panMoved:(YourPanGestureRecognizer*)sender {
[sender cancelGesture]; // This will be called twice
}
Ref: https://developer.apple.com/documentation/uikit/uigesturerecognizer?language=objc
Just set recognizer.state in your handlePan(_ recognizer: UIPanGestureRecognizer) method to .ended or .cancelled
You have a couple ways of handling this:
If you were writing a custom pan gesture recognizer subclass, you could easily do this by calling -ignoreTouch:withEvent: from inside the recognizer when you notice it straying from the area you care about.
Since you're using the standard Pan recognizer, and the touch starts off OK (so you don't want to prevent it with the delegate functions), you really can only make your distinction when you receive the recognizer's target actions. Check the Y value of the translationInView: or locationInView: return values, and clamp it appropriately.