I'd like to enable the user to resize an UITextView. I display a button in the bottom rigth corner of the UITextView such as when the user moves it, the UITextView follow it and is resized.
This is works fine but the text space has a strange behaviour. The text space seems to get smaller and smaller during the resize. So if I resize a lot, go to a big frame and then to a small frame, the text space has become smaller than the frame.
- (id) init{//My subclass of UITextView
self = [super init];
if(self){
//Resize button
//Init ResizeButton
self.resizeButton = [[UIButton alloc]init];
//Image
[self.resizeButton setImageForAllState:[UIImage imageNamed:#"Isosceles-right-triangle.png"]];
//Target
[self.resizeButton addTarget:self action:#selector(wasDragged:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[self.resizeButton addTarget:self action:#selector(wasDragged:withEvent:) forControlEvents:UIControlEventTouchDragOutside];
//Add to view
[self addSubview:resizeButton];
//Self
//text font
[self setFont:[UIFont fontWithName:#"papyrus" size:17]];
}
return self;
}
- (void)wasDragged:(UIButton *)button withEvent:(UIEvent *)event
{
// NSLog(#"wasDragged");// get the touch
UITouch *touch = [[event touchesForView:button] anyObject];
// get delta
CGPoint previousLocation = [touch previousLocationInView:button];
CGPoint location = [touch locationInView:button];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
// move button
button.center = CGPointMake(button.center.x + delta_x,
button.center.y + delta_y);
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width+ delta_x, self.frame.size.height+ delta_y);
}
The view is also draggable
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// Calculate offset
CGPoint pt = [[touches anyObject] locationInView:self];
float dx = pt.x - startLocation.x;
float dy = pt.y - startLocation.y;
CGPoint newcenter = CGPointMake(self.center.x + dx, self.center.y + dy);
// Set new location
self.center = newcenter;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Calculate and store offset, and pop view into front if needed
CGPoint pt = [[touches anyObject] locationInView:self];
startLocation = pt;
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
}
EDIT
Before resizing
Resizing
Back to original size
Well, I have this problem too. The soliution is to subclass UITextView and override setFrame: method. Before setting your wanted frame, set CGRectZero frame for textView. It will fix your problem. Code bellow.
- (void)setFrame:(CGRect)frame
{
[super setFrame:CGRectZero];
[super setFrame:frame];
}
Seems like you could do one of two things:
First: turn off the 'Adjust to fit' of the control
textfield.adjustsFontSizeToFitWidth = NO;
Second: Hardcode a font size into the code
textfield.font = [UIFont fontWithName:#"desired font name" size:10];
Related
I have an UIView inside main view controller which contain 1 imageview.i moved imageview with this method.
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *aTouch = [touches anyObject];
if (aTouch.view == self.sub_View) {
CGPoint location = [aTouch locationInView:self.sub_View];
CGPoint previousLocation = [aTouch previousLocationInView:self.sub_View];
self.imageView.frame = CGRectOffset(self.imageView.frame, (location.x - previousLocation.x), (location.y - previousLocation.y));
}
}
Imageview move perfect. But i have to keep imageview inside UIView when i moved. Problem with this code is when i move imageview it is also moved outside UIView. I have to keep imageview inside UIView and movable only inside UIView.
Please help me to solve this. How to set boundary for imageview so it is movable only inside UIView.
Try below code.
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *aTouch = [touches anyObject];
if (aTouch.view == self.sub_View) {
CGPoint location = [aTouch locationInView:self.sub_View];
CGPoint previousLocation = [aTouch previousLocationInView:self.sub_View];
CGRect newFrame = CGRectOffset(self.imageView.frame, (location.x - previousLocation.x), (location.y - previousLocation.y));
if(newFrame.origin.x < 0)
newFrame.origin.x = 0;
if(newFrame.origin.y < 0)
newFrame.origin.y = 0;
if(newFrame.origin.x + newFrame.size.width > self.frame.size.width)
newFrame.origin.x = self.frame.size.width - self.imageView.frame.size.width;
if(newFrame.origin.y + newFrame.size.height > self.frame.size.height)
newFrame.origin.y = self.frame.size.height - self.imageView.frame.size.height;
self.imageView.frame = newFrame;
}
}
a) You can set the clipsToBounds property of your UIView to YES
b) You can use the fallowing code for moving your UIImageView
self.imageView.center = CGPointMake(MAX(0.0, MIN(location.x - previousLocation.x, self.sub_View.bounds.size.width)), MAX(0.0, MIN(location.y - previousLocation.y, self.sub_View.bounds.size.height)));
I want to create a slide out menu from the right of my main view controller. However I want the slide out menu to appear on top of the main view and not push it to one side.
I've looked at ECSlidingViewController 2 and it allows sliding out from either side, however the slide out menus appear cut off on the exposed edges. It creates the effect of the top view controller moving left or right to reveal the hidden menu under it. I want it to appear that the hidden menu sliding out is going onto of the main view controller and that we can see its left edge so no content is cut off.
Is this at all possible and would ECSlidingViewController 2 work for this?
Edit
This is what I have tried:
To detect the touch started:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
BBAppDelegate *myAppDelegate = (BBAppDelegate*)[UIApplication sharedApplication].delegate;
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
NSArray *myViewArray = [[NSBundle mainBundle] loadNibNamed:#"FilterView" owner:self options:nil];
self.filterView = myViewArray[0];
[myAppDelegate.window addSubview:self.filterView];
if (CGRectContainsPoint(self.filterView.frame, touchLocation)) {
dragging = YES;
oldX = touchLocation.x;
oldY = touchLocation.y;
}
}
And then dragging:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if (dragging) {
CGRect frame = self.filterView.frame;
frame.origin.x = self.filterView.frame.origin.x + touchLocation.x;
frame.origin.y = self.filterView.frame.origin.y + touchLocation.y;
self.filterView.frame = frame;
}
}
However, when I start a touch, the filterView replaces all views and doesn't slide or animate in anyway. I though it was due to not providing a starting point - so I instated self.filterView like so:
self.filterView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 120, 564)];
However changing these values have no effect on the position / size of the view when its displayed.
Edit 2
Okay, I started a new project for testing and managed to get a view on screen to move around by following my touches.
I am using UIPanGestureRecognizer
Like so:
viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *myViewArray = [[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil];
UIView *myView = myViewArray[0];
self.filterView = myView;
self.filterView.frame = CGRectMake(100, 100, 200, 200);
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self
action:#selector(dragging:)];
pan.delegate = self;
[self.filterView addGestureRecognizer:pan];
[self.view addSubview:self.filterView];
}
Then in the dragging method:
-(void)dragging:(UIPanGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateBegan)
{
NSLog(#"Received a pan gesture");
self.panCoord = [gesture locationInView:gesture.view];
}
CGPoint newCoord = [gesture locationInView:gesture.view];
float dX = newCoord.x-self.panCoord.x;
gesture.view.frame = CGRectMake(gesture.view.frame.origin.x+dX, gesture.view.frame.origin.y, gesture.view.frame.size.width, gesture.view.frame.size.height);
}
This allows the view to move horizontally only.
Almost there - what I am trying to figure out now is getting it to snap to a location when the user lifts their finger. And how to move this view when its start position is off screen.
Edit 3
Okay, so I wanted to control the speed and velocity of the view coming out onto screen. If the user swipes quickly, the view goes into place at the speed they swipe. If they swipe slowly, it goes into place slowly. However I also set the lowest time - so if they take a long time - it defaults to the min amount.
Here is the code:
{
float start;
float end;
NSDate *methodStart;
NSDate *methodEnd;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
methodStart = [NSDate date];
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
start = location.x;}
-(void)dragging:(UIPanGestureRecognizer *)gesture{
if(gesture.state == UIGestureRecognizerStateBegan)
{
self.panCoord = [gesture locationInView:gesture.view];
}
CGPoint newCoord = [gesture locationInView:gesture.view];
float dX = newCoord.x-self.panCoord.x;
gesture.view.frame = CGRectMake(gesture.view.frame.origin.x+dX, gesture.view.frame.origin.y, gesture.view.frame.size.width, gesture.view.frame.size.height);
if (gesture.state == UIGestureRecognizerStateEnded){
methodEnd = [NSDate date];
NSTimeInterval executionTime = [methodEnd timeIntervalSinceDate:methodStart];
double time = executionTime;
float distance = start - end;
int distanceOverTime = distance / time;
float veloticty = distanceOverTime / 100;
double miniTimeToUse = 0.5;
double newMinTime;
if (time < miniTimeToUse){
newMinTime = miniTimeToUse;
}else {
newMinTime = time;
}
if (gesture.view.frame.origin.x+dX >= 180){
[UIView animateWithDuration:newMinTime
delay:0
usingSpringWithDamping:0.9
initialSpringVelocity:veloticty
options:0
animations:^{
self.filterView.frame = CGRectMake(300, 100, 200, 200);
} completion:nil];
}
if (gesture.view.frame.origin.x+dX <= 181){
[UIView animateWithDuration:newMinTime
delay:0
usingSpringWithDamping:0.9
initialSpringVelocity:veloticty
options:0
animations:^{
self.filterView.frame = CGRectMake(50, 100, 200, 200);
} completion:nil];
}
}
}
Now just to get it to work on pulling the view off from the side.
I am having issue with collision behavior.I have 2 types of objects falling from up to bottom of the screen and collisioning with image in the bottom of the screen. Collision are working great, but soon as I move the image, it gets rescaled and blinks very often.Thanks for advice.
Code for moving the image.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
//CGPoint svpt = [[touches anyObject] locationInView:self.view];
CGPoint pt = [[touches anyObject] locationInView:self.playerMove];
CGRect frame = [self.playerMove frame];
frame.origin.x += (pt.x - self.startLocation.x);
frame.origin.y += (pt.y - self.startLocation.y);
frame.origin.x = MIN(MAX(frame.origin.x, -10), 240);
frame.origin.y = MIN(MAX(frame.origin.y, 430), 430);
self.playerMove.frame = frame;
[_animator updateItemUsingCurrentState:self.playerMove];
}
Code for collision.
_collision = [[UICollisionBehavior alloc]initWithItems:#[self.hammerImage,self.playerMove]];
[_animator addBehavior:_collision];
Although I don't know where the exact problem is, I will tell you this: UIKitDynamics works by changing the view's frame and transform. So, the problem is probably that you are messing with transform by explicitly modifying frame. Try using CGAffineTransformTranslate instead.
If you want to move an object that has been added to an animator, I think you want to add an attachment behavior to playerMove, and drag the anchor point in your touchesMoved method. So first, create the attachment behavior, and add it to the animator:
self.ab = [[UIAttachmentBehavior alloc] initWithItem:self.playerMove attachedToAnchor:self.playerMove.center];
[_animator addBehavior:self.ab];
Then in your touchesBegan and touchesMoves, do something like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint pt = [[touches anyObject] locationInView:self];
self.currentLocation = pt;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint pt = [[touches anyObject] locationInView:self];
self.ab.anchorPoint = CGPointMake(self.ab.anchorPoint.x + (pt.x - self.currentLocation.x), self.ab.anchorPoint.y + (pt.y - self.currentLocation.y));
self.currentLocation = pt;
}
I currently have an application where a user takes a photo or chooses from their library.
On the next controller it will show the image that was selected/taken.
What I would like to do now is show a view of some sort on top of the image view. The view would be translucent round the edges and have a circle which would show the image beneath (not transulcent). Basically this is selected a part of the image.
I then need to save some how where the view is on screen, as it should also be moveable by the user.
What is the best way to approach this?
I need an overlay view which can be moved. The circle would be a fixed size always the inside shows the imageview beneath and the outside would be a 0.5 translucency so you can still see the image but not completely. Then to save the location of the moved around circle?
---- EDIT -----
This is the example image I have created.
I have a view which has a photo as the UIImageView (bottom layer). On top of this I am trying to add a view (like the picture above). Note, the picture above is actually a png as suggested. However, this overlay is moveable. The circle is transparent (0) so you can completely see the photo below it. The outer (grey) is transparent partially (0.5) so you can still see just not completely.
The top view (circle part) would be moved around on the photo to mark a specific point on the photo. In this example if the circle is moved the side (grey) ends on screen, therefore I would need to make a huge image which takes into account the moving of the view -- which is not the best way to do this surely?
EDIT 2 ----
I now have one UIImageView over the top of the photoView (another UIImageView). The overlay is 4 times the screen with a circle in the middle of it.
When the item is moved I have a gesture recognizer that runs:
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
NSLog(#"Pan Gesture");
gesture.view.center = [gesture locationInView:self.view];
}
At present from the above the UIImageView is moved from the middle point, that way the circle is what looks to be moving when the finger moves.
That is all great, but how do I implement the suggested answers into my handlePan method. So I need to check that the middle point is not too close the edge. Ideally I would like a 50 margin around the screen so the circle does not look to go completely (or mostly) off screen?
I understand from the question that you know how to do the movement of the overlay, so what I meant is just that you can simply use image views big enough to make sure that their border isn't visible.
Let's say the UIImageView would be called
UIImageView *photoImageView;
And the overlay with translucent and semi-translucent areas would be
UIImageView *imageOverlayView;
The way I see it, you need 3 different images for that imageOverlayView, depending on the device it's running on - 1 for iPad, 1 for iPhone 5 and 1 for other iPhones.
Width of that image should be equal to (screen width - circle radius) * 2, height should
CGFloat circleRadius; //this would be the radius of translucent circle
So if you set the frame of the overlay properly:
CGRect imageFrame = photoImageView.frame;
imageOverlayView.frame = CGRectMake(circleRadius - imageFrame.size.width/2, circleRadius - imageFrame.size.height/2, (imageFrame.size.width - circleRadius)*2, (imageFrame.size.height - circleRadius)*2); //origin is set to the middle of the image.
you'd never see the edge of the grey area. MZimmerman6's answer for implementation of the movement is good, but you should also make sure you block the circle from getting out of the borders of underlying image.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:imageOverlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = imageOverlayView.frame;
overFrame.origin.x += delX;
if (overFrame.origin.x < -imageFrame.size.width) {
overFrame.origin.x = -imageFrame.size.width;
}
else if (overFrame.origin.x > 0) {
overFrame.origin.x = 0;
}
overFrame.origin.y += delY;
if (overFrame.origin.y < -imageFrame.size.height) {
overFrame.origin.y = -imageFrame.size.height;
}
else if (overFrame.origin.y > 0) {
overFrame.origin.y = 0;
}
[overlayView setFrame:overFrame];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:imageOverlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = imageOverlayView.frame;
overFrame.origin.x += delX;
if (overFrame.origin.x < -imageFrame.size.width) {
overFrame.origin.x = -imageFrame.size.width;
}
else if (overFrame.origin.x > 0) {
overFrame.origin.x = 0;
}
overFrame.origin.y += delY;
if (overFrame.origin.y < -imageFrame.size.height) {
overFrame.origin.y = -imageFrame.size.height;
}
else if (overFrame.origin.y > 0) {
overFrame.origin.y = 0;
}
[overlayView setFrame:overFrame];
}
EDIT
With your pan gesture recognizer, checking if you aren't going too far needs a little change to the handlePan: method.
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
CGPoint newCenter = [gesture locationInView:self.view];
if (newCenter.x < 50) {
newCenter.x = 50;
}
else if (newCenter.x > self.view.frame.size.width - 50) {
newCenter.x = self.view.frame.size.width - 50;
}
if (newCenter.y < 50) {
newCenter.y = 50;
}
else if (newCenter.y > self.view.frame.size.height - 50) {
newCenter.y = self.view.frame.size.height - 50;
}
gesture.view.center = newCenter;
}
If that 50 points margin is equal to circle radius, this will make sure your circle able to touch the edges of the screen, but unable to go beyond them.
I would first initialize some UIImageView with a png image in it that will easily handle this moving frame with a hole in the center. You can the add this to your screen. Once this is done, use the touchesBegan, touchesMoved and other touch commands to find whether a user is touching that box and from that determine how far the user has moved from the previous point. Use this difference to then add or subtract values from the images current frame origin or center, and voila you have a moving box.
EDIT
In View.h
CGPoint prevPoint;
float delX;
float delY;
UIView *overlayView;
In View.m
-(void) viewDidLoad {
overlayView = [UIView alloc] initWithFrame:CGRectMake(100,100,100,100)];
[overlayView setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:overlayView];
UIImageView *circImage = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,overlayView.frame.width,overlayView.frame.height)];
[circImage setImage:[UIImage imageNamed:#"SomeImage.png"]];
[overlayView addSubview:circImage];
[self.view setNeedsDisplay];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
prevPoint = [touch locationInView:overlayView];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:overlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = overlayView.frame;
overFrame.x += delX;
overFrame.y += delY;
[overlayView setFrame:overFrame];
[self.view setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:overlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = overlayView.frame;
overFrame.x += delX;
overFrame.y += delY;
[overlayView setFrame:overFrame];
[self.view setNeedsDisplay];
}
I have a problem, I want to drag a label with finger, and I can't even register a touch on it. What can be a problem? self is my viewcontroller and self.label is my label.
EDIT:
My label is made programmatically over an uiimageview. UserInteractionEnables is set to YES.
I am not sure what is wrong, here is code in its entirety:
Init:
UIImage *myImage = [UIImage imageNamed:#"fish.jpg"];
UIImageView *mainImageView = [[UIImageView alloc] initWithImage:myImage];
self.view.frame = CGRectMake(0, 0, 320, 480);
self.view = mainImageView;
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 100)];
self.label.userInteractionEnabled = YES;
self.label.text = #"Hello, World";
[self.view addSubview:self.label];
Dragging:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([[touch view] isEqual:self.label])
{
NSLog(#"touch on label");
self.startLocation = [[touches anyObject] locationInView:self.label];
// startLocation is a CGPoint declare globaly in .h..and lblName is your UILabel
}
}
- (void) touchesMoved:(NSSet *)touches withEvent: (UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([touch view] == self.label)
{
CGPoint pt = [[touches anyObject] previousLocationInView:self.label];
CGFloat dx = pt.x - self.startLocation.x;
CGFloat dy = pt.y - self.startLocation.y;
CGPoint newCenter = CGPointMake(self.label.center.x + dx, self.label.center.y + dy);
self.label.center = newCenter;
}
}
EDIT 2:
I have also tried this code, but it doesn't work too. I think the problem is not in these methods but with registering touches.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.startLocation = [[touches anyObject] locationInView:self.view];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint point = [[touches anyObject] locationInView:self.label];
self.label.center = CGPointMake(self.label.center.x + point.x - self.startLocation.x, self.label.center.y + point.y - self.startLocation.y);
}
Make sure you check "User Interaction Enabled" on the label in interface builder, or if you you doing it in code, userInteractionEnabled=YES
By default, UILAbel does not respond to touches. You need to enable the userInteractionEnabled property.
label.userInteractionEnabled = YES;