I have a UIView
#property (nonatomic, strong) IBOutlet UIView *previewView;
previewView will ultimately display the preview of what is going on in my camera on my iPhone using the following preview layer.
#property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
I have constraints set up on my previewView in storyboards and therefore am running into the following issue.
self.previewLayer.frame = self.previewView.bounds; // This doesn't work
self.previewLayer.videoGravity = AVLayerVideoGravityResize;
[self.previewView.layer addSublayer:self.previewLayer];
previewLayer gets added to the previewView subLayer, but it does not show in the right parameters (width and height). I think this is due to using constraints on autolayout.
How would I fix this?
Update: I also tried the following:
self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspect;
CALayer *rootLayer = [previewView layer];
rootLayer.frame = self.previewView.bounds;
[rootLayer setMasksToBounds:YES];
[self.previewLayer setFrame:[rootLayer bounds]];
[rootLayer addSublayer:self.previewLayer];
And it appears to show like this: http://cl.ly/image/3T3o0O1E3U17
Getting close, but still very much off.
Okay, I solved it.
The problem is that when you use autolayout, the frames/bounds of your UIViewController's subviews can change after you have set up your previewLayer, without the previewLayer itself being updated accordingly! This is because the layout of CALayers is solely controlled by one's own code (see also this related answer: https://stackoverflow.com/a/27735617/623685).
To solve this, you have to override the viewDidLayoutSubviews method of your UIViewController to update the previewLayer's position and bounds (and possibly others) every time the subviews' layout changes.
In the following, I will describe how I solved it in detail:
My case was actually a bit more complicated, because I am using the OpenCV iOS framework 2.4.9. Here, the preview layer is created and managed by an OpenCV class called CvVideoCamera. Therefore I had to create a subclass of CvVideoCamera, to which I added the following method:
- (void)updatePreviewLayer
{
self.customPreviewLayer.bounds = CGRectMake(0, 0, self.parentView.frame.size.width, self.parentView.frame.size.height);
[self layoutPreviewLayer];
}
Explanation: parentView is a UIView you pass to CvVideoCamera in order to specify where the preview layer should be positioned and how big it should be. customPreviewLayer is the preview layer managed by CvVideoCamera. And layoutPreviewLayer is a method that basically updates the layer's position: https://github.com/Itseez/opencv/blob/2.4/modules/highgui/src/cap_ios_video_camera.mm#L211
Then I simply called this method from my UIViewController like so:
- (void)viewDidLayoutSubviews
{
NSLog(#"viewDidLayoutSubviews");
[super viewDidLayoutSubviews];
[self.myCameraView updatePreviewLayer];
}
The main idea is the same - when using Autolayout, the frame of the preview layer needs to be set after all view constraints have been applied. The viewDidLayoutSubviews method is used for this - same as in the original answer but without complications of using any frameworks other than the Apple's AVFoundation.
The following code is based on an Apple's sample (AVCam-iOS: Using AVFoundation to Capture Images and Movies). But the code was modified to use updateViewConstraints method to programmatically set all constraints instead of the Storyboard approach used in the sample. Relevant fragments are shown below:
#interface QRScannerViewController ()
#property (nonatomic) UIView *previewView;
#end
#implementation QRScannerViewController
{
AVCaptureSession *_captureSession;
AVCaptureVideoPreviewLayer *_captureVideoPreviewLayer;
dispatch_queue_t _sessionQueue;
}
#pragma mark - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.previewView];
_captureSession = [AVCaptureSession new];
_captureVideoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession];
_captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_sessionQueue = dispatch_queue_create("av_session_queue", DISPATCH_QUEUE_SERIAL);
[self.view setNeedsUpdateConstraints];
dispatch_async(_sessionQueue, ^{
[self configureCaptureSession];
});
[self.previewView.layer addSublayer:_captureVideoPreviewLayer];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (_isCaptureDeviceConfigured) {
[_captureSession startRunning];
}
}
- (void)viewDidLayoutSubviews
{
_captureVideoPreviewLayer.frame = self.previewView.bounds;
}
#pragma mark - getters / initializers
- (UIView *)previewView
{
if (_previewView) {
return _previewView;
}
_previewView = [UIView new];
_previewView.backgroundColor = [UIColor blackColor];
_previewView.translatesAutoresizingMaskIntoConstraints = NO;
return _previewView;
}
- (void)configureCaptureSession
{
[_captureSession beginConfiguration];
// Code omitted, only relevant to the preview layer is included. See the Apple's sample for full code.
...
_captureVideoPreviewLayer.connection.videoOrientation = initialVideoOrientation;
...
}
#pragma mark - Autolayout
- (void)updateViewConstraints
{
[self.previewView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor constant:0.0].active = YES;
[self.previewView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor constant:-40.0].active = YES;
[self.previewView.widthAnchor constraintEqualToConstant:300].active = YES;
[self.previewView.heightAnchor constraintEqualToAnchor:self.previewView.widthAnchor constant:0.0].active = YES;
[super updateViewConstraints];
}
Related
I am pretty stuck trying to create a IOS module for Titanium/Appc i am trying to intergrate https://github.com/antiguab/BAFluidView so i can use it in titanium.
I have followed the module tutorials have it working fine with just the standard view but when i try to add BAFluidView it doesnt work.
I have included the classes in xcode and have the code below.
#import "ComExampleFluidView.h"
#import "TiUtils.h"
#import "BAFluidView.h"
#import "UIColor+ColorWithHex.h"
#implementation ComExampleFluidView
- (void)initializeState
{
// Creates and keeps a reference to the view upon initialization
square = [[UIView alloc] initWithFrame:[self frame]];
BAFluidView *view = [[BAFluidView alloc] initWithFrame:view.frame];
[view fillTo:#1.0];
view.fillColor = [UIColor colorWithHex:0x397ebe];
[view startAnimation];
[square addSubview:view];
[self addSubview:square];
[super initializeState];
}
-(void)dealloc
{
// Deallocates the view
RELEASE_TO_NIL(square);
[super dealloc];
}
-(void)frameSizeChanged:(CGRect)frame bounds:(CGRect)bounds
{
// Sets the size and position of the view
[TiUtils setView:square positionRect:bounds];
}
-(void)setColor_:(id)color
{
// Assigns the view's background color
square.backgroundColor = [[TiUtils colorValue:color] _color];
}
#end
header file is
#import "TiUIView.h"
#interface ComExampleFluidView: TiUIView {
UIView *square;
}
#end
Can anyone give some suggestions on this?
since you are trying to bridge a native view, you need some layout helpers that are required to handle the Titanium-layout-system properly. Please check modules like ti.googlemaps, especially the initialization of views. In addition, your custom setters like setColor need to apply the color to your BAFluidView, not to your UIView, so you need to keep a reference of that inside your header. I guess the ti.googlemaps example should explain all concepts you are looking for. Good luck!
I have subclassed a UITextField and trying to add a subview by calling [self.superView addSubview:newView] but this is not working and self.superview.class returns null.
How can one add a subview to the superView
The view is showing on the screen and done using Storyboard
#interface NewTextField : UITextField <UITextFieldDelegate>
#end
and then
#implementation MBTextField
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self addWhiteLine]; // here im adding a view
}
return self;
}
- (void)addWhiteLine{
UIView *div = [UIView new];
div.frame = CGRectMake(self.frame.origin.x,
self.frame.origin.y + self.frame.size.height+borderWidth,
self.frame.size.width,
borderWidth);
div.backgroundColor = [borderColor colorWithAlphaComponent:0.3f];
[self.superview addSubview:div];
}
Solution: accepted answer and comments by #mackworth. Frame of the view had to be changed to following.
div.frame = CGRectMake(0,
self.frame.size.height - borderWidth,
self.frame.size.width,
borderWidth);
If self.superview is nil, then at the time addWhiteLine is being called, self hasn't been added to the view hierarchy yet. This actually makes sense given that you're calling it from the initWithCoder. Whoever's loading this view has to alloc/init before adding it to a superview. As a fix, why add to self.superview, shouldn't you add to self instead?
Edit: As discussed in comments, another problem is that the line is being drawn outside the frame, so it should be self.frame.origin.y + self.frame.size.height-borderWidth instead.
I have 2 Custom View classes(CustomView_A, CustomView_B) derived from UIView. I have UIViewController that should be able to switch between views at run-time..
What so far, I have done is.. in the Storyboard, I am using CustomView_A class as the View class.
#interface MyViewController: UIViewController
#property (nonatomic, weak) CustomView_A *customView_A;
Now I have the second CustomView_B class and I want to change view of MyViewController's view to CustomView_B at run-time.
How can I do that? Thanks in advance..
okay, here is the code as you want -
in your MyViewController.h put -
#property (nonatomic, retain) CustomView_A *customView_A;
#property (nonatomic, retain) CustomView_B *customView_B;
-(void)switchView; // to switch the views.
in your MyViewController.m put -
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.customView_A = [[CustomView_A alloc]initWithFrame:self.view.frame];
self.customView_A.backgroundColor = [UIColor redColor];
UIButton *trigger = [UIButton buttonWithType:UIButtonTypeRoundedRect]; // Just take this button so that your switchView methods will get called on click of this method.
[trigger setFrame:CGRectMake(10, 10, 50, 30)];
[trigger setTitle:#"Click" forState:UIControlStateNormal];
[trigger addTarget:self action:#selector(switchView) forControlEvents:UIControlEventTouchUpInside];
[self.customView_A addSubview:trigger];
[self.view addSubview:self.customView_A];
self.customView_B = [[CustomView_B alloc]initWithFrame:self.view.frame];
self.customView_B.backgroundColor = [UIColor yellowColor];
self.customView_B.hidden = YES;
[self.view addSubview:self.customView_B];
}
- (void)switchView
{
[UIView animateWithDuration:10 delay:10 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.customView_A.hidden = YES;
self.customView_B.hidden = NO;
} completion:nil];
}
Do opposite when you again want to switch the views.
Don't. What you're describing is an essential misunderstanding of UIViewController. Once a UIViewController instance has a view, that is its view forever.
If you want two different views then either:
Use two view controllers (for example, you can present view controller B and its view on top of view controller A and its view, using a modal segue), or
Make at least one of those views not be owned by a view controller: just place that view in front of the other view and later remove it again, at will.
Try this:
- (IBAction)changeView {
if (self.customView_A.hidden == YES) {
self.customView_A.hidden = NO;
self.customView_B.hidden = YES;
//You should use a UIView animation here to do this.
}
else {
self.customView_A.hidden = YES;
self.customView_B.hidden = NO;
//Same here
)
}
In your viewDidLoad add the view to CGRectZero
- (void)viewDidLoad {
self.customView_A = [[CustomView_A alloc]initWithFrame:CGRectZero];
[self.view addSubview:self.customView_A];
//do the same with the other custom view
}
Sorry if the code is a little faulty, I didn't use Xcode to type this up.
I'm looking for popover in iPhone and i want to make it like iOS 5 Reader feature:
After little research i found WEPopover and FPPopover but i'm looking if there anything like this API built-in iphone SDK.
You could make a UIView with some custom artwork and display it with an animation on top of your view as a "popover" with some buttons like so:
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(25, 25, 100, 50)]; //<- change to where you want it to show.
//Set the customView properties
customView.alpha = 0.0;
customView.layer.cornerRadius = 5;
customView.layer.borderWidth = 1.5f;
customView.layer.masksToBounds = YES;
//Add the customView to the current view
[self.view addSubview:customView];
//Display the customView with animation
[UIView animateWithDuration:0.4 animations:^{
[customView setAlpha:1.0];
} completion:^(BOOL finished) {}];
Don't forget to #import <QuartzCore/QuartzCore.h>, if you want to use the customView.layer.
Since iOS8 we are now able to create popovers, that will be the same on iPhone, as on iPad, which would be especially awesome for those who make universal apps, thus no need to make separate views or code.
You can get the class as well as demo project here: https://github.com/soberman/ARSPopover
All you need to do is subclass UIViewController, conform to the UIPopoverPresentationControllerDelegate protocol and set desired modalPresentationStyle along with the delegate value:
// This is your CustomPopoverController.m
#interface CustomPopoverController () <UIPopoverPresentationControllerDelegate>
#end
#implementation CustomPopoverController.m
- (instancetype)init {
if (self = [super init]) {
self.modalPresentationStyle = UIModalPresentationPopover;
self.popoverPresentationController.delegate = self;
}
return self;
}
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationNone; //You have to specify this particular value in order to make it work on iPhone.
}
Afterwards, instantiate your newly created subclass in the method from which you want to show it and assign two more values to sourceView and sourceRect. It looks like this:
CustomPopoverController *popoverController = [[CustomPopoverController alloc] init];
popoverController.popoverPresentationController.sourceView = sourceView; //The view containing the anchor rectangle for the popover.
popoverController.popoverPresentationController.sourceRect = CGRectMake(384, 40, 0, 0); //The rectangle in the specified view in which to anchor the popover.
[self presentViewController:popoverController animated:YES completion:nil];
And there you have it, nice, neat blurred popover.
I am trying to animate a UIView (block-based animation) but I cannot make the timing work (duration, etc.). The animation runs, because the changes are done, and the completition output message is shown, but it just ignores the duration and instantly makes the changes.
I asume there is something wrong with the way I am doing things, but I just cannot figure out where the mistake is. Let me explain what I am trying to do:
I have a UIViewController (viewcontroller) that handles two objects (view1 and view2) of a UIView sublcass in which I have defined the touchesEnded method.
The idea is that when view1 has been touched, I want to animate view2. So what I did was to implement an animation method in the UIView subclass, a notification in the touchesEnded method (also in the UIView subclass), and
a trigger method in the controller that would call the animation of the second view. Something like this:
// In the UIView subclass:
- (void) myAnimation{
[UIView animateWithDuration:2.0
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
self.alpha = 0.5;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
// In the UIView subclass:
- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event {
[pParent viewHasBeenTouched]; // pParent is a reference to the controller
}
// In the UIViewController:
- (void) viewHasBeenTouched {
[view2 myAnimation];
}
(The animation and work flow is in fact a bit more complex, but this simple example just fails to work)
If I place the animation somewhere else, it may work, for example just after initializing the views in the init method of the controller. But if I try to do this forward-backward calls, the animation will just ignore the duration and execute in one step.
Any ideas? What have I missed about touches and gestures that I should know? Thank you.
NEW INFORMATION ADDED TO THE ORIGINAL POST:
The AppDelegate does nothing but this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[TestViewController alloc] init];
self.window.rootViewController = self.viewController;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
The TestViewControler.h is just this:
#import <UIKit/UIKit.h>
#import "TestView.h"
#interface TestViewController : UIViewController{
TestView* myView;
}
- (void) viewHasBeenTouched;
#end
and the viewDidLoad only does this:
- (void)viewDidLoad{
[super viewDidLoad];
myView = [[TestView alloc] initWithParent:self];
[self.view addSubview:myView];
}
Finally, the UIView has a TestView.h which looks like this:
#import <UIKit/UIKit.h>
#class TestViewController; // Forward declaration
#interface TestView : UIView{
TestViewController* pParent;
}
- (id)initWithParent:(TestViewController*) parent;
- (void) myAnimation;
#end
And the init method used is:
- (id)initWithParent:(TestViewController *)parent{
CGRect frame = CGRectMake(50, 20, 100, 200);
self = [super initWithFrame:frame];
if (self) pParent = parent;
self.backgroundColor = [UIColor blueColor];
return self;
}
So... with this simple code I posted, the error occurs. As I said, the animation does change the alpha, but with no delay or timing. It's just instantaneous. Any ideas with this added information?
Thank you again for the help.
I found the problem. I want to apologize first for all the people how have studied my problem and have lost valuable time on it.
All the second part I posted in the original question was copy and pasted indeed, it was the first part that had those two mismatching errors because they came from a more complex code. And that code works without problem as you have all stated. The problem is that I did not copy and paste a method that I thought I had removed from the project (and which is called at the start of the application):
- (BOOL)shouldAutorotateToInterfaceOrientation (UIInterfaceOrientation)interfaceOrientation {
// ... More code
[UIView setAnimationsEnabled:NO];
// ... More code
return YES;
}
It obviously needs no further explanation.
Again thank you very much for your time, and my sincere apologizes.