Redrawing GradientButton (Subclassed from UIButtons) - uibutton

I am using the GradientButton class in my application. When I change the frame of a button the CAGradientLayer doesn't change for the new frame. The CAGradientLayer is the size of the old frame dimensions which makes the button look really poor. The added width or height then is just a solid color and doesn't have the Gradient. One solution would be to delete the button and re-add it, however this is not the path I want to take. Is there a method that I can call to redraw the GradientButton so that the gradient layer encompasses the new button dimensions?
Update: removeFromSuperview and addSubview doesn't fix issue.

Created this method to solve problem:
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
shineLayer.frame = self.layer.bounds;
highlightLayer.frame = self.layer.bounds;
}

Related

UIButton inside a UIView not working after UIView is animated

I have seen many questions in this forum which gives answer to this topic "UIButton inside a UIView, when animated doesn't work", but after having tried out the answers like
a) UIViewAnimationOptionAllowUserInteraction to the options
b) subView.setUserInteractionEnabled = YES
c) [button addTarget:self action:#selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
none of them is working for me :-(
Here is the scenario. App is in landscape mode and a UIView called menuView is placed at x=480,y=0. Its height=200 and width=150. So, when I tap the button on the top right corner, the following code is executed
- (IBAction)showMenu {
[menuView setUserInteractionEnabled:YES];
[optionsButton setUserInteractionEnabled:YES];
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationCurveEaseOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.view.frame = CGRectOffset(self.view.frame, 0, 200);
}
completion:NULL];
}
Output?: View is appearing, but optionsButton inside the menuView is not clickable. I hooked up to an event, did an NSLog(#"Options button clicked"), but nothing happens :-(
Please tell me what I am doing wrong?
Update: When I place the subview that is "menuView" inside the self.view, then upon running and click the optionsButton, I get the NSLog message. Only if I specify the origin.x of menuView to be 480 and above, it doesn't work.
If you can see the UIButton, its userInteractionEnabled (YES by default!) is set, and its superview's userInteractionEnabled in the whole hierarchy is YES - you should be able to interact with it.
An animation will never change your userInteractionEnabled property! So what you are doing there is simply unnecessary. If you try to enable interaction during animation - that's a different story and is just an option passed to the animation message.
So if that's not your problem (and is probably not), then I guess one of the superview's frame is cropping the UIButton!
Now you see, if a UIButton (or any UIView) is outside a UIView's frame, it can still be visible, unless clipsToBounds is set on the superview.
And the outcome of that situation is: You can see me, but you can't touch me.
In my case missing a constraint on the height of the containing view caused the view height to be 0. Buttons were visible, below the view, but untouchable.
For me it was all about an overlooked mistake in the animation code itself. Where my new height was being called I was actually calling my new width, and where my new width was being called I was actually calling my new height, therefor causing the UIView to be super long yet super narrow, thus pushing all content within the center of view, outside of the view reach.
Change your UIViews background color to Red so that you can actually see where it animates to.
If your UIView is within another view, turn on clipSubviews so that you only see what is actually enabled within the subView.
Run your project.
If your UIView is not its normal height and width, more than likely you have overlooked some code in your animation call.
Make sure that your x, y, width, height are being shown in the correct orders within the animation code.
In my case, it seems like my animated transitioning is not yet completed. I forgot to to put completeTransition:Bool at the end of my animateTransition:context method.

UIScrollView - Custom Map - Prevent marker subview on map from scaling with map

I have a custom map of a limited area, and have it set up to correctly show the users' location. The map is a 1600px square image within a UIScrollView.
I have a crosshair image to show the current location of the user, which at zoomScale 1.0 is the desired size. When I pinch and zoom the scrollView, the crosshair scales with it. I would like to have the subview remain the same size on screen.
I haven't been able to find any information on this, what would be the best way to go about this?
If there is anything I can provide you with to help the answer, please let me know.
Many thanks!
EDIT -
Having looked in to this further, there is a UIScrollViewDelegate method - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale which I tried using to take the marker's current center and size, then adjust, but this only scales at the end of the zoom. I would prefer to have the marker remain the same size while the user is zooming.
EDIT 2-
Cake has provided a great answer below, but I haven't been able to implement this in the way I imagined it would be.
I have the UIImageView as a placeholder, with alpha set to 0. This placeholder moves around relative to the map to show the user location. This operates as I expect it to. Unfortunately, this resizes with the map, as it is a subview of the map (so it stays in place).
Taking Cake's below answer, I have created the non-scaling crosshair image, and added it as a sibling subview to the scrollview. The maths, once Cake had pointed them out, were quite simple to get the new frame for the crosshair:
CGPoint ULPC = userLocationPlaceholder.center;
float zs = scrollView.zoomScale;
CGRect newFrame = CGRectMake(((ULPC.x * zs) - scrollView.contentOffset.x) - 20, ((ULPC.y * zs) - scrollView.contentOffset.y) - 20, 40, 40);
Where the image is 40points wide. This matches the centers perfectly.
The problem I now have is that I cannot get the crosshair image to stay locked to the placeholder.
I have tried using a self calling animation as such:
-(void)animeUserLocationAttachment
{
[UIView animateWithDuration:0.05
delay:0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear )
animations:^{
userLocationDotContainer.frame = newFrame;
} completion:^(BOOL finished){
// Call self
[self animateUserLocationAttachment];
}];
}
As soon as I start scrolling/zooming, this locks the animation so that the crosshair just sits in place until I release the scrolling/zooming, then it correctly updates it's location.
Is there any way I can get around this, or an alternative method I can apply?
Many thanks
EDIT 3 -
I've re-accepted Cake's answer as it covers 90% of the issue. Further to his answer I have implemented the ScrollViewDelegate methods scrollViewWillBeginDragging: andscrollViewWillBeginDecelerating: to scale the placeholder to match the current size of the crosshair relative to the map, show the placeholder (that is a subview of the map image) and hide the crosshair image. The delegate method scrollviewWillBeginZooming:withView: does not show the placeholder because it scales with the map. As Cake recommends, I'll make a new question for this issue.
The counterpart methods (scrollViewDidEndZooming:withView:atScale:, scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:`) all hide the placeholder, and re-show the crosshair.
The question is old but for the future similar questions I've recently resolved a similar problem applying the hint of Andrew Madsen of another post.
I'had a UIScrollView, with an UIImageView in it. Attached to the UIImageView I had many MKAnnotationView (those are my subviews that I didn't want scaling with the superview).
I did subclass UIImageView and implement setTransform: method like here:
#import "SLImageView.h"
#implementation SLImageView
- (void)setTransform:(CGAffineTransform)transform
{
[super setTransform:transform];
CGAffineTransform invertedTransform = CGAffineTransformInvert(transform);
for (id obj in self.subviews)
{
if ([obj isKindOfClass:[MKAnnotationView class]])
{
[((UIView *)obj) setTransform:invertedTransform];
}
}
}
#end
This works perfectly!
Mick.
Create another crosshair image that's associated with the view or view controller that contains the scrollview. Then have this one always snap to the center of the crosshair image you already have. Then, hide your original crosshair image. Then you can avoid having the scrollview scale the disassociated crosshair, and it should stay the same size.
Relative coordinate systems
Each view in cocoa touch has a frame property that has an origin. In order to position an object owned by one view properly relative to another view, all you have to do is figure out the differences in their origins. If one view is a subview of another, then this isn't too difficult.
Get the origin of the container view
Get the location of the subview inside of the container view
Get the origin of the subview
Calculate the difference in the positions of the origins
Get the location of the object you want to overlap (relative to the subview)
Calculate the location of the object you want to overlap relative to the container view
Move your crosshair to this position
Swift equivalent for Mick's answer:
class MapContainerView:UIView {
#IBOutlet var nonScalingViews: [UIView]!
override var transform: CGAffineTransform {
didSet {
guard let nonScalingViews = nonScalingViews else {
return
}
let invertedTransform = CGAffineTransformInvert(transform)
for view in nonScalingViews {
view.transform = invertedTransform
}
}
}
}

Draw Over Image

I'm working on some drawing code. I have that portion working great.
I want to draw over an image, but I want to still be able to see the detail of the image, the black lines, etc.
What I am working on is making a transparent UIImageView that holds the image.
I'm not sure how to get this set up properly though.
Should this be added above the other UIImageView that I color on or below it?
Here's what I have so far:
- (void)viewDidLoad
{
[super viewDidLoad];
topImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 46, 320, 370)];
[topImageView setImage:[UIImage imageNamed:#"imagesmall.png"]];
topImageView.alpha = 1.0;
topImageView.layer.opacity = 1.0;
topImageView.layer.opaque = NO;
[self.view addSubview:topImageView];
[topImageView release];
}
Thoughts anyone?
Yes, you can draw views over other views. They are drawn in the order that they're added as subviews, unless you reorder them after that.
You may need to set the opaque property for some views (this is distinct from and overrides their layer opacity), and set their backgroundColor to nil. UIImageView seems to be transparent by default, as long as its image is; some other UIView subclasses are not.
So, just what is your overlay going to be? If you just need to display one image over another, what you have here seems to work already. If you need to draw some lines programmatically, you'll need to do this:
Create a subclass of UIView.
Implement its drawRect method to display the content you need.
When you add your custom view on top of the background image, make sure it is not opaque and has no backgroundColor.
A common problem here is to find that your foreground is working, but the background isn't being loaded properly. To make sure the background is there, set the alpha of the foreground view to 0.5. You won't want to do that in production, but it will allow you to verify that both views exist.

How to create a "stretchable" UIView

I have a UIView that contains another UIView. The outer UIView draws a border around the inner UIView via drawRect. (The border is too complicated to be drawn via CALayer properties.)
At present, when I animate the resizing of the outer UIView, its drawRect method is called once at the beginning of the animation and the result is stretched or shrunk. This does not look good.
I am looking for a way to either redraw the content at every step of the animation, or find a way to achieve the same visual effect. (The result should be similar to the resizing of a stretchable UIImage.)
You should change view's content type to:
your_view.contentMode = UIViewContentModeRedraw;
And it will redraw each time its frame changes.
I ended up adding subviews with autoresizing masks that kept them positioned correctly during the animation.
You need to send a [UIView setNeedsToDisplay] to the view for every time the frame size is changed, you could try overriding the setFrame: method like
- (void)setFrame:(CGRect)r
{
[super setFrame:r];
[self setNeedsToDisplay];
}

Custom Keyboard: inputView: how to change the Keyboard size?

I implemented the textfield with a custom keyboard with the "setInputView" function.
But i have a problem: my keyboard frame is not a standard iphone keybord frame.
The question is:
How can i change the size of my custom keyboard?
I know some functions like: UIKeyboardFrameBeginUserInfoKey, ..etc.
Please Note:
The iPhone keyboard frame is = 0,264,320,216
My custom keyboard frame is = 0,0,320,460
Hoping for your kind collaboration,
Best regards...
P
It turns out that the default behaviour of the custom input view that you assign to the UITextField's property is to resize the view to the same frame as the default keyboard. Try setting (I use the name InputViewController for my input view, but you can use whatever you want):
inputViewController = [[InputViewController alloc] initWithNibName:#"InputViewController" bundle:nil];
inputViewController.delegate = self;
inputViewController.view.autoresizingMask = UIViewAutoresizingNone; // This is the code that will make sure the view does not get resized to the keyboards frame.
For more detailed information, you can look at this link, which is provided by Apple.:
If UIKit encounters an input view with an UIViewAutoresizingFlexibleHeight value in its autoresizing mask, it changes the height to match the keyboard.
Hope that Helps!
To set the keyboard inputView with the same size as the native keyboard just do this:
inputView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
To set your own frame do this:
inputView.autoresizingMask = UIViewAutoresizingNone;
From Apple:
You have a lot of flexibility in defining the size and content of an input view or input accessory view. Although the height of these views can be what you’d like, they should be the same width as the system keyboard. If UIKit encounters an input view with a UIViewAutoresizingFlexibleHeight value in its autoresizing mask, it changes the height to match the keyboard. There are no restrictions on the number of subviews (such as controls) that input views and input accessory views may have. For more guidance on input views and input accessory views, see iOS Human Interface Guidelines.
I had the same problem. I solved it by registering for UIKeyboardDidShowNotification (UIKeyboardWillShowNotification did not work, unfortunately) and then changing the view size after the keyboard was shown. However, it still had the white box on top of the keyboard when it was moving up. This worked fine for me because it is coming in over a UITextView with a white background. If you were coming in over any other colored objects, however, it would look a little ugly before the view was properly resized. You can solve that by setting the background color to clearColor.
// Add this while initializing your view
self.backgroundColor = [UIColor clearColor]; // Needed because we can't resize BEFORE showing the view. Otherwise you will see an ugly white box moving up w/ the keyboard
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
CGRect rect = self.frame;
rect.size.height = 164;
self.frame = rect;
}
Also if you're using a UIViewController to design your inputView, don't use the UIViewController.view... it seems to have a lot of problems getting resized incorrectly on rotate regardless of the AutoresizeMask.
What worked for me was to take my existing UI and use Editor > Embed In > View. Then create a new outlet, and pass that outlet as the inputView. Suddenly the resize on rotate bugs disappeared.
For me msgambel's solution didn't work. But the approach right, I was playing with the inputView's autoresizingMask. Former I had different setting, but the right way to avoid white extra space over the custom keyboard is:
I applied this just for the outermost view.