27

I have a UITextView where the user can create notes and save into a plist file. I want to be able to show lines just like a normal notebook. The problem I have is that the text won't align properly.

The image below explains the problem quite well.

My print screen explains the problem quite well

This is the background I use to create the lines like the Notes.app enter image description here

This is my code for creating the background for my UITextView:

textView.font            = [UIFont fontWithName:@"MarkerFelt-Thin" size:19.0]; 
textView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed: @"Notes.png"]];

I know that the UIFont.lineHeight property is only available in > iOS 4.x.

So I wonder if there is another solution to my problem?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Fernando Redondo
  • 1,557
  • 3
  • 20
  • 38

2 Answers2

56

You should try and draw your lines programmatically rather than using an image. Here's some sample code of how you could accomplish that. You can subclass UITextView and override it's drawRect: method.

NoteView.h

#import <UIKit/UIKit.h>
@interface NoteView : UITextView <UITextViewDelegate> {
}
@end

NoteView.m

#import "NoteView.h"

@implementation NoteView

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:0.6f alpha:1.0f];
        self.font = [UIFont fontWithName:@"MarkerFelt-Thin" size:19];
    }
    return self;
}

- (void)drawRect:(CGRect)rect {

    //Get the current drawing context   
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    //Set the line color and width
    CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.2f].CGColor);
    CGContextSetLineWidth(context, 1.0f);
    //Start a new Path
    CGContextBeginPath(context);

    //Find the number of lines in our textView + add a bit more height to draw lines in the empty part of the view
    NSUInteger numberOfLines = (self.contentSize.height + self.bounds.size.height) / self.font.leading;

    //Set the line offset from the baseline. (I'm sure there's a concrete way to calculate this.)
    CGFloat baselineOffset = 6.0f;

    //iterate over numberOfLines and draw each line
    for (int x = 0; x < numberOfLines; x++) {
        //0.5f offset lines up line with pixel boundary
        CGContextMoveToPoint(context, self.bounds.origin.x, self.font.leading*x + 0.5f + baselineOffset);
        CGContextAddLineToPoint(context, self.bounds.size.width, self.font.leading*x + 0.5f + baselineOffset);
    }

    //Close our Path and Stroke (draw) it
    CGContextClosePath(context);
    CGContextStrokePath(context);
}

@end

MyViewController.h

#import <UIKit/UIKit.h>
#import "NoteView.h"
@interface MyViewController : UIViewController <UITextViewDelegate> {

    NoteView *note;
}

@property (nonatomic, retain) NoteView *note;

@end

MyViewController.m

#import "MyViewController.h"
#import "NoteView.h"

#define KEYBOARD_HEIGHT 216

@implementation MyViewController
@synthesize note;

- (void)loadView {
    [super loadView];
    self.note = [[[NoteView alloc] initWithFrame:self.view.bounds] autorelease];
    [self.view addSubview:note];
    note.delegate = self;
    note.text = @"This is the first line.\nThis is the second line.\nThis is the ... line.\nThis is the ... line.\nThis is the ... line.\nThis is the ... line.\nThis is the ... line.\n";
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [note setNeedsDisplay];
}

- (void)textViewDidBeginEditing:(UITextView *)textView {
    CGRect frame = self.view.bounds;
    frame.size.height -= KEYBOARD_HEIGHT;
    note.frame = frame;
}

- (void)textViewDidEndEditing:(UITextView *)textView {
    note.frame = self.view.bounds;
}

- (void)dealloc {
    [note release];
    [super dealloc];
}

Take a look at Apple's documentation for Managing the Keyboard, specifically "Moving Content That Is Located Under the Keyboard". It explains how to listen for NSNotifcations and adjust your views properly.

Adolfo
  • 4,969
  • 4
  • 26
  • 28
  • Thanks for the answer Adolfo. I will try this as soon as possible! Do you use a UIScrollView or _just_ a UITextView? I hope that your NoteView class will make my UIScrollView dissapear :) – Fernando Redondo Mar 28 '11 at 10:53
  • Now I've tried it. The drawRect method works perfectly but increases the [NoteView frame] for each line that appears. So now I will try to add the NoteView as a subview for the UIScrollView. Is this how I'm suppose to use your class? – Fernando Redondo Mar 28 '11 at 11:30
  • Hi Fernando. You don't need a UIScrollView as a parent view. You should just be able to add it to a regular view and it should scroll if the text overflows the view's frame. I'm not clear as to what you mean by the frame increases for each line. – Adolfo Mar 28 '11 at 22:06
  • 1
    Ok, I think I follow you now regarding the lines appearing as you add more text. I'll edit the answer to just add the height of the view to the content size's height and that should cover the whole view with lines. – Adolfo Mar 29 '11 at 01:51
  • Hi Adolfo. Now the line creation works great! There is one more problem though. When you show the keyboard you must decrease the height on the NoteView, and when I do that when you have lets say 20 lines of text some text will move up to show the line where the cursor is. And when this happends the text no longer alignes with the lines created in the drawRect method. I'm going to give this a try know and see if I can solve it in some way. I think the problem is that your class doesn't support the possibility to change it's height to make room for the keyboard? – Fernando Redondo Mar 29 '11 at 08:06
  • Hi again Adolfo. The result from what I explained in my last comment above can also be achieved by clicking in the NoteView, and press "Return" on the Keyboard a couple of times. If you do that the lines doesn't correspond with the text. – Fernando Redondo Mar 29 '11 at 13:17
  • Fernando, I updated the answer again with a sample `MyViewController` that can manage a note view. You should, however, implement a more robust system for managing the keyboard. – Adolfo Mar 29 '11 at 21:20
  • Thank you very much for your answers Adolfo. You've helped me very much! What kind of robust system? The system for managing the keyboard should just be to resize the views when it's visible? Or have I wrong? – Fernando Redondo Apr 03 '11 at 09:22
  • That is correct. Check out the link at the bottom of the answer - it will take to you the Apple documentation where you can find a recommended way of resizing your views to accommodate for the keyboard. The code above makes assumptions that the keyboard is a specific size, which can be different based on the orientation of the device and possibly any future OS changes...and there aren't any pretty animations. – Adolfo Apr 04 '11 at 03:41
  • 1
    Ok I understand, thank you. If you click enter and create new lines with text to the degree that the text would be invisible under the keyboard if your solution didn't stop that from happening, can you then see that the lines isn't aligned correctly anymore? If you just type three lines everything is perfect, but as soon as it gets more that the NoteView.frame.size.height the lines is incorrectly drawn. Does this happend to you? – Fernando Redondo Apr 06 '11 at 06:29
  • The code is really good. Is it possible to reduce the gap between the lines? If Yes, how? any ideas? – Satyam Aug 22 '11 at 16:30
  • 1
    Ok this is absolutely fantastic. I am using it with a text view loaded from a XIB - just make a UITextView and set its class to NoteView, done. Works brilliantly. I made one change, I am starting the for loop from 1 so that it doesn't draw a line above the first line of text - looks prettier, and more like a real notepad. But really this answer goes above and beyond what could be expected from StackOverflow. – n13 Jan 31 '12 at 10:36
  • You could instead call setNeedsDisplay in the layoutSubviews method of the TextView subclass. This way all the background drawing-related code is kept in one reusable class. Thanks for the code! – Justin Driscoll Feb 22 '12 at 16:49
  • 5
    don't forget to add `self.contentMode = UIViewContentModeRedraw;` in `initWithFrame:` this way the text and lines scroll together. Otherwise, absolutely awesome solution! – Joey J Mar 06 '12 at 08:35
  • 2
    I'm not sure when it got broken, but font.leading property always return 0 for me. Instead you should use font.lineHeight which works totally fine on iOS 9 – Allan Spreys Nov 08 '15 at 06:46
  • 1
    Message from debugger: Terminated due to memory issue on iOS 9 – Engnyl Jan 27 '16 at 08:51
  • @Adolfo I am facing "Message from debugger: Terminated due to memory issue" error. I found that commenting the for loop in drawRect method is the reason for the memory crash. iOS 9. Is there anything I can do to avoid the crash and draw the lines, too? – viral Mar 03 '16 at 13:54
0

I think the problem is with your image, the yellow space over the line is creating the problem.

You should edit the image.

And nice work.

Irfan
  • 4,301
  • 6
  • 29
  • 46
Iqbal Khan
  • 4,587
  • 8
  • 44
  • 83
  • It seemes to be a combination of two things: The yellow space which I remove made it look like the line height was the same on each line. The problem became very clear when I fixed that: There is a top padding on the text in the UITextView which stops the text from perfect alignation. Margin can be solved with contentInset but not padding. Does anybody know how to fix the top padding? The equivalent in CSS is: padding-top:0px – Fernando Redondo Mar 21 '11 at 09:33
  • yes u r right. i check this by my self. but still not able to solve that problem. – Iqbal Khan Mar 21 '11 at 09:51
  • The contentInset property doesn't work on my solution because of the background is moving with the contentInset. It's just the content that needs to be moved. I can't understand why the UITextView by default have a padding on like 15px or so. I don't know if this is adjustable after my research, I think I need to look at some alternative way of solving it. Have somebody here done this in the past perhaps? – Fernando Redondo Mar 21 '11 at 09:59
  • i want to do this in my last client project. then i leave all that line thing due to many difficulties. but if u find a solution then u should share with us. – Iqbal Khan Mar 21 '11 at 10:09