Dailycode.info

Short solution for short problems

iOS: UITextField in UICollectionView get behind keyboard.

OK, I already posted a solution to move the view up when the keyboard shows so your textfield or view is not behind, but on top of the keyboard. 

Recently I had a similar problem, but this solution was not working exactly is I wanted. The problem was that when clicked in a textfield that was located in the top of the collection view, the field got moved out of screen. 

The first and easy part is moving the view up when the keyboard shows, just by implementing the solution found on all forums registering the keyboardwilshow and hide notifications:

- (void)viewWillAppear:(BOOL)animated

{

    // register for keyboard notifications

    [[NSNotificationCenterdefaultCenter] addObserver:self

                                             selector:@selector(keyboardWillShow)

                                                 name:UIKeyboardWillShowNotification

                                               object:nil];

    

    [[NSNotificationCenterdefaultCenter] addObserver:self

                                             selector:@selector(keyboardWillHide)

                                                 name:UIKeyboardWillHideNotification

                                               object:nil];

}

 

- (void)viewWillDisappear:(BOOL)animated

{

    // unregister for keyboard notifications while not visible.

    [[NSNotificationCenterdefaultCenter] removeObserver:self

                                                    name:UIKeyboardWillShowNotification

                                                  object:nil];

    

    [[NSNotificationCenterdefaultCenter] removeObserver:self

                                                    name:UIKeyboardWillHideNotification

                                                  object:nil];

 

}

These 2 selectors simply move the view up or down, I made the move up function general:

//method to move the view up/down whenever the keyboard is shown/dismissed

+(void)setViewMovedUp:(BOOL)movedUp AndView:(UIView *) vw

{

    int movementDistance = 80; // tweak as needed

    if ( UIInterfaceOrientationIsLandscape([[UIApplicationsharedApplication]statusBarOrientation]))

    {

        movementDistance = kOFFSET_FOR_KEYBOARDHOR;

    }

    else

    {

        movementDistance = kOFFSET_FOR_KEYBOARDVER;

    }

    const float movementDuration = 0.3f; // tweak as needed

    

    int movement = (movedUp ? -movementDistance : movementDistance);

    

    [UIViewbeginAnimations: @"anim"context: nil];

    [UIViewsetAnimationBeginsFromCurrentState: YES];

    [UIView setAnimationDuration: movementDuration];

    vw.frame = CGRectOffset(vw.frame, 0, movement);

    [UIViewcommitAnimations];

 

}

So this function is called when the keyboard moves up or down like this:

-(void)keyboardWillShow

{

    [GeneralFunctionssetViewMovedUp:YES AndView:self.view];

 

}

 

-(void)keyboardWillHide

{

    [GeneralFunctionssetViewMovedUp:NOAndView:self.view];

 

}

This already works, but will shift the top textfields out of screen:

 

When clicked on one of the top fields, you can see the field moving out of screen, I clicked in the textfield with text menu, it's out of screen after the keyboard shows:

Now how to solve this. I had to find out when clicked in the collection view in a textfield that would appear behind the keyboard if shown.

To solve this I made a delegate function in the cell view controller that would be called when the textfield edit did begin. 

-(void) collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath

{

    selectedIndexPath = indexPath;

}

 

-(void) EditDidBegin:(UICollectionViewCell *)cell

{

    NSIndexPath * path = [self.colTranslations indexPathForCell:cell];

    [self SetMove:path];

}

 

-(void) SetMove:(NSIndexPath *) indexPath

{

    UICollectionViewLayoutAttributes *attributes = [self.colTranslations layoutAttributesForItemAtIndexPath:indexPath];

    

    CGRect cellRect = attributes.frame;

    

    CGRect cellFrameInSuperview = [self.colTranslations convertRect:cellRect toView:[self.colTranslations superview]];

    

    NSLog(@"%f",cellFrameInSuperview.origin.y);

    if (cellFrameInSuperview.origin.y > kOFFSET_FOR_KEYBOARDHOR)

    {

        moveScreen=YES;

    }

    else

    {

        moveScreen=NO;

    }

 

}

 

So the function SetMove will calculate where clicked in the superview and chooses if the keyboard needs to be shifted up or not.

Just add these checks to the selectors of the keyboard will show and hide notifications and it will work:

-(void)keyboardWillShow

{

    if (moveScreen==YES)

    {

        [GeneralFunctionssetViewMovedUp:YESAndView:self.view];

        up=YES;

    }

    else

    {

        up=NO;

    }

}

 

-(void)keyboardWillHide

{

    if (moveScreen==YES && up==YES)

    {

        [GeneralFunctionssetViewMovedUp:NOAndView:self.view];

    }

 

}

Now do not forget to declare the bool 

bool moveScreen;

 

bool up;

 
And the result is this:

You see the Menu field that was clicked is still visible.

Now if I click in a field that is behind the keyboard:

The screen moves up!

If you hide the keyboard, the screen will get back in its original position.


Using LINQ to concatinate an object property from a list of objects

Just put this in a little demo to make it clear. I created a simple object Person, with ID, FirstName and LastName.

Now I fill a List with 3 persons (later I'll fill it with 20000 to do a performance check):

List<Person> persons = newList<Person>();

{

    newPerson(){ID=0, FirstName="Mark", LastName="Deraeve"},

    newPerson(){ID=1, FirstName="Priscilla", LastName="Lauwerijssen"},

    newPerson(){ID=2, FirstName="Anne-Lisa", LastName="Deraeve"}

};

Instead of looping over the list and concatinating it, like this:

start = DateTime.Now;

foreach (Person p in persons)

{

    sPersons = String.Format("{0},{1}", sPersons, p.FirstName);

}

//remove front ,

Console.WriteLine("With Loop: " + sPersons);

I can also just use 1 line to accomplisch this:

Console.WriteLine("With LINQ: " +string.Join(",", persons.Select(p => p.FirstName)));

Result:

 

Now let's say you do not want all records, this makes it even more easy, just put a Where before the Select and you got it!

Let's only show person named 'Deraeve':

Console.WriteLine("With LINQ: " +string.Join(",", persons.Where(p => p.LastName == "Deraeve").Select(p => p.FirstName)));

Result:

 

Now last, lets try to see performance difference. I'll fill the list with 20000 persons. This is where the power comes in. (The number 188889 or 188890(+1 for the extra comma) is the length of the result string)

 

Amazingly, the LINQ only uses 3 or 4 milliseconds, the old for loop takes 1-3 (There is a difference when using String.Format (slower) or just "string + ','+ "string") seconds. This can really make a big difference in today's apps.

So here the complete code so you can test it yourself:

Code of the console app:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace TestConsoleApp

{

    classProgram

    {

        staticvoid Main(string[] args)

        {

            List<Person> persons = newList<Person>();

            /*{

                new Person(){ID=0, FirstName="Mark", LastName="Deraeve"},

                new Person(){ID=1, FirstName="Priscilla", LastName="Lauwerijssen"},

                new Person(){ID=2, FirstName="Anne-Lisa", LastName="Deraeve"}

            };*/

 

            for (int i = 0; i < 20000; i++)

            {

                persons.Add(newPerson() { ID = i, FirstName = string.Format("Name{0}", i), LastName = "Deraeve" });

            }

 

            string sPersons="";

            DateTime start = DateTime.Now;

            TimeSpan ts;

 

            start = DateTime.Now;

            sPersons = string.Join(",", persons.Select(p => p.FirstName));

            ts = DateTime.Now.Subtract(start);

            Console.WriteLine("With LINQ: " + sPersons.Length);

            Console.WriteLine(String.Format("Seconds:{0} Miliseconds{1} ", ts.Seconds, ts.Milliseconds));

            Console.WriteLine("");

 

            sPersons = "";

 

            start = DateTime.Now;

            sPersons = string.Join(",", persons.Where(p => p.LastName == "Deraeve").Select(p => p.FirstName));

            ts = DateTime.Now.Subtract(start);

            Console.WriteLine("With LINQ and Where: " + sPersons.Length);

            Console.WriteLine(String.Format("Seconds:{0} Miliseconds{1} ", ts.Seconds, ts.Milliseconds));

            Console.WriteLine("");

 

            sPersons="";

 

            start = DateTime.Now;

            foreach (Person p in persons)

            {

                sPersons = String.Format("{0},{1}", sPersons, p.FirstName);

            }

            ts = DateTime.Now.Subtract(start);

 

            Console.WriteLine("With for loop: " + sPersons.Length);

            Console.WriteLine(String.Format("Seconds:{0} Miliseconds{1} ",ts.Seconds,ts.Milliseconds));

            Console.WriteLine("");

 

            sPersons = "";

 

            start = DateTime.Now;

            foreach (Person p in persons)

            {

                if (p.LastName == "Deraeve")

                {

                    sPersons = String.Format("{0},{1}", sPersons, p.FirstName);

                }

            }

 

            ts = DateTime.Now.Subtract(start);

 

            Console.WriteLine("With for loop and if: " + sPersons.Length);

            Console.WriteLine(String.Format("Seconds:{0} Miliseconds{1} ", ts.Seconds, ts.Milliseconds));

            Console.WriteLine("");

 

            sPersons = "";

 

            start = DateTime.Now;

            foreach (Person p in persons)

            {

                sPersons = sPersons + ',' + p.FirstName;

            }

            ts = DateTime.Now.Subtract(start);

 

            Console.WriteLine("With for loop without format: " + sPersons.Length);

            Console.WriteLine(String.Format("Seconds:{0} Miliseconds{1} ", ts.Seconds, ts.Milliseconds));

            Console.WriteLine("");

 

            Console.ReadKey();

        }

    }

}

Code of the Person class:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace TestConsoleApp

{

    publicclassPerson

    {

        publicstring FirstName { get; set; }

 

        publicstring LastName { get; set; }

 

        publicint ID { get; set; }

 

    }

}

Things even get better for the Join statement when we add the last name to the result:

sPersons = string.Join(",", persons.Where(p => p.LastName == "Deraeve").Select(p => p.FirstName + ' ' + p.LastName));

VS

foreach (Person p in persons)

{

    sPersons = String.Format("{0},{1} {2}", sPersons, p.FirstName, p.LastName);

}


Result: