Sliding UITextFields around to avoid the keyboard

It's an iPhone post because I finally can. Here's a good way to slide your view around when editing UITextFields so that they never get trapped under the onscreen keyboard.

I'll be giving a talk at the Brisbane Cocoaheads meeting this Monday evening (Oct 6). Come along and heckle.

Hidden text fields

The iPhone's onscreen keyboard occupies the bottom 216 pixels on screen (162 in landscape mode). That's around half the screen, so if you ever have a text field you want to edit in the bottom half of the screen, it needs to move or it will get covered.

You can just animate the whole window upwards by the height of the keyboard when editing a text field in the bottom half but this doesn't work well for text fields in the middle (they can get moved too far up).

Instead, I'm going to show you a method which divides the window as follows:

slidingsections.png

Everything in the top section will stay still when edited. Everything in the middle section will animate upwards by a fraction of the keyboard's height (proportional to the field's height within the middle section). Everything in the bottom section will animate upwards by the keyboard's full height.

Implementing the delegate methods
All of the methods shown here should go into a view controller probably the "main" view controller for the current screen (i.e. the visibleViewController of the current UINavigationController , your RootViewController or other top-level view controller).

You will need to make this view controller the delegate (in Interface Builder) for every UITextField you want to animate.

Your view controller will need the following instance variable:

CGFloat animatedDistance;

The following constants should also be declared somewhere (likely the top of the view controller's implementation file):

static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3;
static const CGFloat MINIMUM_SCROLL_FRACTION = 0.2;
static const CGFloat MAXIMUM_SCROLL_FRACTION = 0.8;
static const CGFloat PORTRAIT_KEYBOARD_HEIGHT = 216;
static const CGFloat LANDSCAPE_KEYBOARD_HEIGHT = 162;
Animate upwards when the text field is selected

Get the rects of the text field being edited and the view that we're going to scroll. We convert everything to window coordinates, since they're not necessarily in the same coordinate space.

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
CGRect textFieldRect =
[self.view.window convertRect:textField.bounds fromView:textField];
CGRect viewRect =
[self.view.window convertRect:self.view.bounds fromView:self.view];

So now we have the bounds, we need to calculate the fraction between the top and bottom of the middle section for the text field's midline:

    CGFloat midline = textFieldRect.origin.y + 0.5 * textFieldRect.size.height;
CGFloat numerator =
midline - viewRect.origin.y
- MINIMUM_SCROLL_FRACTION * viewRect.size.height;
CGFloat denominator =
(MAXIMUM_SCROLL_FRACTION - MINIMUM_SCROLL_FRACTION)
* viewRect.size.height;
CGFloat heightFraction = numerator / denominator;

Clamp this fraction so that the top section is all "0.0" and the bottom section is all "1.0".

    if (heightFraction < 0.0)
{
heightFraction = 0.0;
}
else if (heightFraction > 1.0)
{
heightFraction = 1.0;
}

Now take this fraction and convert it into an amount to scroll by multiplying by the keyboard height for the current screen orientation. Notice the calls to floor so that we only scroll by whole pixel amounts.

    UIInterfaceOrientation orientation =
[[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationPortrait ||
orientation == UIInterfaceOrientationPortraitUpsideDown)
{
animatedDistance = floor(PORTRAIT_KEYBOARD_HEIGHT * heightFraction);
}
else
{
animatedDistance = floor(LANDSCAPE_KEYBOARD_HEIGHT * heightFraction);
}

Finally, apply the animation. Note the use of setAnimationBeginsFromCurrentState: — this will allow a smooth transition to new text field if the user taps on another.

    CGRect viewFrame = self.view.frame;
viewFrame.origin.y -= animatedDistance;

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];

[self.view setFrame:viewFrame];

[UIView commitAnimations];
}
Animate back again

The return animation is far simpler since we've saved the amount to animate.

- (void)textFieldDidEndEditing:(UITextField *)textField
{
CGRect viewFrame = self.view.frame;
viewFrame.origin.y += animatedDistance;

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];

[self.view setFrame:viewFrame];

[UIView commitAnimations];
}
Since we're writing the delegate methods...

This next method has nothing to do with animation but since we're writing the delegate methods for a UITextField , this is essential. It dismisses the keyboard when the return/done button is pressed.

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
Result

For a window that looks like this:

textfieldwindow.png

Editing text fields in the top, middle and bottom sections will look like the following:

slidingpositions.png

Notice in particular how the middle section remains in the middle of the visible area after the keyboard appears. This is the primary benefit of the presented approach.

The Sliding Window Protocol is a data communication protocol that is used to ensure reliable and error-free transmission of data over a network. This protocol is used in the data link layer of the OSI model and is responsible for managing the flow of data between two devices. The Sliding Window Protocol works by dividing the data to be transmitted into smaller packets. These packets are then transmitted over the network and are acknowledged by the receiver. The sender will keep transmitting packets until it receives an acknowledgement from the receiver. The receiver will keep acknowledging the packets it receives until it receives all the packets successfully. Here is a simple implementation of the Sliding Window Protocol in Python: ``` import socket # Sender function def sender(): # Create a socket object s = socket.socket() host = socket.gethostname() port = 12345 # Bind the socket to a specific address and port s.bind((host, port)) # Listen for incoming connections s.listen(1) print('Waiting for connection...') # Accept the connection from the receiver conn, addr = s.accept() print('Connection from:', addr) # Define the window size and other variables window_size = 4 base = 0 next_seq_num = 0 packets = ['packet1', 'packet2', 'packet3', 'packet4', 'packet5', 'packet6', 'packet7', 'packet8'] # Send the packets to the receiver while True: # Send the packets within the window while next_seq_num < base + window_size: packet = packets[next_seq_num] conn.send(packet.encode()) print('Sent:', packet) next_seq_num += 1 # If all packets have been sent, break out of the loop if next_seq_num == len(packets): break # Wait for an acknowledgement from the receiver ack = conn.recv(1024).decode() print('Received ACK:', ack) # Update the base if the acknowledgement is for the first packet in the window if ack == str(base): base += 1 # If all packets have been sent and acknowledged, break out of the loop if base == len(packets): break # Close the connection conn.close() # Receiver function def receiver(): # Create a socket object s = socket.socket() host = socket.gethostname() port = 12345 # Connect to the sender s.connect((host, port)) print('Connected...') # Define the window size and other variables window_size = 4 base = 0 next_seq_num = 0 packets_received = [] # Receive the packets from the sender while True: # Receive the packets within the window while next_seq_num < base + window_size: packet = s.recv(1024).decode() packets_received.append(packet) print('Received:', packet) next_seq_num += 1 # If all packets have been received, break out of the loop if next_seq_num == len(packets_received): break # Send an acknowledgement to the sender for the first packet in the window ack = base s.send(str(ack).encode()) print('Sent ACK:', ack) # If all packets have been received and acknowledged, break out of the loop if base == len(packets_received): break # Close the connection s.close() # Main function if __name__ == '__main__': sender() receiver() ``` In this implementation, the sender and receiver functions are defined separately. The sender creates a socket object and binds it to a specific address and port. It then listens for incoming connections from the receiver. Once the connection is established, it sends packets to the receiver within a specified window size. It waits for an acknowledgement from the receiver before sending the next set of packets. The receiver creates a socket object and connects to the sender. It receives packets from the sender within a specified window size and sends an acknowledgement to the sender for the first packet in the window. It waits for the next set of packets before sending another acknowledgement. Overall, the Sliding Window Protocol is an important protocol for ensuring reliable and error-free transmission of data over a network.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值