How to make a screenshot of view with animation in progress - ios

I need to create blur effect for navigation bar when I'm playing animation on view under it. Animation is pretty simple CAKeyframeAnimation instance. But even when I create single snapshot at random moment with any method from list below, it does not work:
[UIWindow drawViewHierarchyInRect: afterScreenUpdates:YES/NO]
[CALayer renderInContext:]
[UIView snapshotViewAfterScreenUpdates:YES/NO]
I see just blank image instead.
Is it possible to capture an animation in motion? Mb there's some different way to blur animation?

Very simple use FXBlurView: https://github.com/nicklockwood/FXBlurView
It has a property which does dynamic blurring so you don't have to take screenshots at each point.
#property (nonatomic, getter = isDynamic) BOOL dynamic;
This property controls whether the FXBlurView updates dynamically, or only once when the view is added to its superview. Defaults to YES. Note that is dynamic is set to NO, you can still force the view to update by calling setNeedsDisplay or updateAsynchronously:completion:. Dynamic blurring is extremely cpu-intensive, so you should always disable dynamic views immediately prior to performing an animation to avoid stuttering. However, if you have mutliple FXBlurViews on screen then it is simpler to disable updates using the setUpdatesDisabled method rather than setting the dynamic property to NO
PS: Make sure you add all frameworks necessary to make it work

The simplest way to blur a view is to add UIToolbar,just change the alpha value to change the blur.
self.toolbar = [[UIToolbar alloc]initForAutoLayout];
[self.view addSubview:self.toolbar];
[self.toolbar autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:0];
[self.toolbar autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:0];
[self.toolbar autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:NAVBAR_HEIGHT];
[self.toolbar autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0];
self.toolbar.alpha = 0.5;

Related

iOS custom keyboard frame

I am making a custom keyboard and I need to draw something above it. I know that apple disallowed to do that straight, but I'm going to try it myself.
What if I make keyboard's view transparent with the height of a window and place an opaque subview with buttons? In this case I can draw on that transparent view. Can I do that somehow? If not, can I get current view controller's view to draw on it? If yes, is it a bad idea?
Here what i've tried in my KeyboardViewController.m:
[self.view setAlpha:0.5];
[self setModalPresentationStyle:UIModalPresentationOverFullScreen];
self.providesPresentationContextTransitionStyle = YES;
self.definesPresentationContext = YES;
You can use the inputAccessoryView property of whatever control is firstResponder.

Moving Background of a View

I have a really large image that I want to use as a background image of a view. However, I don't want to display the entire image at once; I want only a part of the image to be displayed, and then I want to animate it to display other parts of it, similar to the "infinite background" in games (only not infinite in my case ;)).
What is the best way to do this? Will I have to separate the image in several pieces and then somehow animate the transition between the pieces, or is there a better way?
How about having UIScrollView as a background view? You can then put UIImageView inside that scroll view and control scroll view's contentOffset as needed.
I found the solution. This piece of code does the magic:
self.backgroundImageView = [[UIImageView alloc] initWithFrame: self.view.bounds];
self.backgroundImageView.image = [UIImage imageNamed:#"background.png"];
self.backgroundImageView.contentMode = UIViewContentModeBottomLeft;
[self.view addSubview: self.backgroundImageView];
The key was setting the contentMode to UIViewContentModeBottomLeft.

Pattern to fill the entire screen

Every screen of my app has a common tint. Its not a background. Its a pattern that fills the entire screen and it is top of all the views. You can see the pattern flow continuously from one view to another inside the same screen. And it neither obscures other elements nor participate in event handling.
I tried implementing it with this code in my ViewController.
UIColor* texture = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Texture.png"]];
UIView* tintView = [[UIView alloc] initWithFrame:self.view.bounds];
[tintView setBackgroundColor:texture];
[tintView setAlpha:0.5];
[self.view addSubview:tintView];
But it doesn't pass on touches to the views behind it.
tintView shouldn't participate in any event handling. Rather it should let other elements behind it, handle the events like they do it normally.
Other way of doing it is set this as a background of the view property of a UIViewController and set a common alpha for all other subviews of view to show the pattern behind. That will be redundant in most ways.
Any better way of doing this?
Make your tintView a subclass of UIView and implement the hitTest:withEvent: method, returning nil. This will make your view transparent to touches. Or set userInteractionEnabled to NO.
Set the background color with a Textured image
UIImage *bgimg = [UIImage imageNamed:#"Texture.png"];
self.view.backgroundColor = [UIColor colorWithPatternImage:bgimg];

Why on iPad, self.clearsContextBeforeDrawing = NO; won't make the drawRect not clear itself before drawing

I have a TestView inherited from UIView, and everything is drawn on the screen using its drawRect method.
But if I don't want drawRect to clear the view before drawing, I used
self.clearsContextBeforeDrawing = NO;
self.opaque = NO; // also added because the doc says the rect
// passed to drawRect will be filled with
// background color first if it is set to YES
in the initWithFrame method.
The drawRect method is invoked by using
[self.view setNeedsDisplay];
in the ViewController event handler. (for touchesMoved events)
But still, everything is cleared before anything is drawn? How to make it work?
I think this answers your question. From the answer:
You cannot prevent the contents from being erased by doing the following:
[self setClearsContextBeforeDrawing: NO];
This is merely a hint to the graphics engine that there is no point in having it pre-clear the view for you, since you will likely need to re-draw the whole area anyway. It may prevent your view from being automatically erased, but you cannot depend on it.

UISegmentedControl does not respect divider images set for UIControlStateDisabled

I'm using the new UIAppearance API in iOS 5 to style a UISegmentedControl with custom graphics. I need to be able to set some segments to be disabled at times during execution, but the UIAppearance methods don't seem to allow me to set a divider image for the UIControlStateDisabled state.
I'm calling:
[[UISegmentedControl appearance] setDividerImage:disabledSelectedImage
forLeftSegmentState:UIControlStateDisabled
rightSegmentState:UIControlStateSelected
barMetrics:UIBarMetricsDefault];
where disabledSelectedImage is a resizable image from this resource:
Yet when I set the left segment to be disabled ([UISegmentedControl setEnabled:forSegmentAtIndex:]), the result is this:
You can clearly see that the UISegmentedControl has defaulted to use the UIControlStateNormal-UIControlStateNormal divider image.
It seems perfectly happy for me to set a background image using UIControlStateDisabled
[[UISegmentedControl appearance] setBackgroundImage:disabledImage
forState:UIControlStateDisabled
barMetrics:UIBarMetricsDefault];
(and respects the image I supply while in the disabled state) but not a divider image. Has anyone come across this or found a solution?
I've decided that this must be an iOS bug and have filed a radar with Apple. My solution to the problem for now is to remove segments, rather than disabling them.
A bit of an ugly workaround but i managed to fix it with the following until apple fixes it itself.
First you need to subclass UISegmentedControl and add the following:
#implementation MJSegmentedControl
- (void)layoutSubviews
{
[super layoutSubviews];
NSInteger cachedIndex = self.selectedSegmentIndex;
self.selectedSegmentIndex = 0;
self.selectedSegmentIndex = cachedIndex;
}
#end
I haven't had a need to use the appearance controls of iOS 5 yet but if all else fails you could add the resizable image as a child of the segmented control to cover up the ugliness. It's a hack, but it may work and will be relatively forwards-compatibile. Just be certain to set the autosizing masks appropriately.
I had the same issue and it really seems to be a bug. However I've found a solution (a workaround).
I've used XIB file with a controller. In XIB file segmented control was just placed and all of the customisations were done in -viewDidLoad method.
Then I've created a UIView subclass which represented entire view in the XIB. It made possible moving all view customisation code to the -awakeFromNib method of this UIView subclass. After moving this code the divider images were set properly.
As suggested by Fernando in this thread:
Customizing UISegmentedControl in iOS 5
You can try to dispatch your UISegmentedControl settings on the main queue via:
dispatch_async(dispatch_get_main_queue(),^{
// disable part of the segmented control
[self.eventScopeSegmentedControl setEnabled:NO forSegmentAtIndex:2];
});
I did this in viewDidLoad and it worked fine for a while but when my app is really busy at startup, this doesn't always work. I'm guessing there's a race condition that may still revert any settings you made when the appearance proxy goes to work.
I added another ugly hack to make this call in viewWillAppear (after the call to super:viewWillAppear) with a flag (set from viewWillLoad) to ensure this only runs once.
There is actually a pretty simple way to get this done. The current behavior is obviously a bug so this is not an ideal solution but simply a workaround that works beautifully. Namely, use an additional UIView as a "disabled visual cue".
The general steps:
Add a UIView as a sibling to the UISegmentedControl. Ensure the UIView is in front of the UISegmentedControl
Apply the desired color and a transparency to the UIView to match your app skin
Move the UIView to be exactly on top of the UISegmentedControl
Shape the UIView to have the exact size top of the UISegmentedControl
Apply a rounded corner to the UIView to mirror the exact shape of the UISegmentedControl
When the UISegmentedControl is supposed to be disabled, simply show the UIView and disable the user interaction on the UISegmentedControl.
When the UISegmentedControl is supposed to be enabled, simply hide the UIView and enable the user interaction on the UISegmentedControl.
In both cases do not change the UISegmentedControl.enabled property.
Note that it seems like a lot of steps but all of this can be coded in so to add support for disabling your custom UISegmentedControl becomes pretty much a 1 liner after you add this to a configure segmented control method.
Here is how my custom segmented control looks when applying this solution:
Enabled Segmented Control
"Disabled" Segmented Control
Here are some code snippets of interest:
Shape the UIView to match the UISegementedControl (load time configuration)
UISegmentedControl* segmentedControl = ...
//Segmented Control disabled visual cue view
UIView* view = ...
//Step #2
view.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6];
//Step #3 and #4
view.frame = segmentedControl.frame;
//Step #5
view.layer.cornerRadius = 5
view.clipsToBounds = YES;
//Ensure this is disabled by default
view.userInteractionEnabled = NO;
Enable/"Disable" UISegementedControl (runtime state change)
BOOL segmentedControlEnabled = ...
if(segmentedControlEnabled) {
segmentedControl.userInteractionEnabled = YES;
view.hidden = YES;
} else {
segmentedControl.userInteractionEnabled = NO;
view.hidden = NO;
}
That's it.
-

Resources