2013-01-08 23 views
17

Tôi muốn làm một ký tự đầu dòng thả trong một UILabel chỉ sử dụng thuộc tính attributedTextNSAttributedString. Như thế này:Thả nắp bằng NSAttributedString

http://www.interpretationbydesign.com/wp-content/uploads/2009/02/drop_caps.gif

tôi đã thử nghiệm với việc điều chỉnh đường cơ sở cho một loạt các ký tự đầu tiên đến một giá trị tiêu cực, và nó hoạt động cho việc sắp xếp trên cùng của char đầu tiên với phía trên cùng của phần còn lại của dòng đầu tiên. Nhưng tôi đã không tìm thấy bất kỳ cách nào để làm cho các dòng khác chảy bên phải của nhân vật bị giới hạn thả.

Điều này có thể được giải quyết bằng cách sử dụng NSAttributedString only hoặc tôi có phải tách chuỗi và tự hiển thị chuỗi đó bằng Văn bản chính không?

+0

Bạn có thể đặt ảnh chụp màn hình về những gì bạn đã đạt được cho đến nay không? Và mã thử nghiệm? –

+1

Tôi sử dụng Thả chuỗi giới hạn trong ứng dụng của mình, nhưng để thực hiện, tôi đã sử dụng UIWebView và HTML được sử dụng để thực hiện tác vụ đó. Tôi không chắc chắn nó có thể được thực hiện trong một UILabel – Youssef

Trả lời

7

CoreText không thể thực hiện thao tác thả xuống vì nó bao gồm các dòng được tạo thành từ đường chạy glyph. Một nắp thả sẽ bao gồm nhiều dòng không được hỗ trợ.

Để đạt được hiệu ứng này, bạn sẽ phải vẽ nắp một cách riêng biệt và sau đó vẽ phần còn lại của văn bản trong đường dẫn xung quanh nó.

Ngắn câu chuyện ngắn: không thể có trong UILabel, có thể, nhưng một chút công bằng khi làm việc với CoreText.

Các bước để làm điều đó với CoreText là:

  • tạo framesetter cho nhân vật duy nhất.
  • có giới hạn của nó
  • tạo ra một con đường mà phụ tùng ra khung của nắp thả
  • tạo framesetter cho các nhân vật còn lại với con đường này
  • vẽ hình tượng đầu tiên
  • vẽ còn lại
+0

Tôi không tìm cách để làm điều đó với một UILabel, tôi muốn làm điều đó với Core Text, nhưng chỉ sử dụng một 'NSAttributesString'. Không phải một vài bộ định khung, hoặc bộ khung hình với một đường dẫn. – PeyloW

+0

Như tôi đã nói, điều đó là không thể với một chuỗi được phân bổ duy nhất. Xem phần giới thiệu CoreText của tôi để hiểu cách hoạt động của khung. http://www.cocoanetics.com/2011/01/befriending-core-text/ – Cocoanetics

1

Không phải là một giải pháp hoàn hảo, nhưng bạn nên cung cấp DTCoreText một lần thử và hiển thị số NSString bình thường của bạn là formatted HTML. Trong HTML, bạn có thể "Thả nắp" một chữ cái.

3

Không, điều này không thể thực hiện với chỉ NSAttributedString và bản vẽ chuỗi tiêu chuẩn.

Vì nắp thả là thuộc tính của một đoạn, CTParagraphStyle sẽ phải chứa thông tin về giới hạn thả. Thuộc tính duy nhất trong số CTParagraphStyle ảnh hưởng đến thụt đầu dòng là kCTParagraphStyleSpecifierFirstLineHeadIndent, nhưng điều đó chỉ ảnh hưởng đến dòng đầu tiên.

Không có cách nào để nói cho CTFramesetter cách tính số lần bắt đầu cho hàng thứ hai và nhiều hàng hơn.

Cách duy nhất là xác định thuộc tính của riêng bạn và viết mã để vẽ chuỗi bằng cách sử dụng CTFramesetterCTTypesetter xác nhận thuộc tính tùy chỉnh này.

14

Như mọi người khác đã đề cập, bạn không thể thực hiện việc này chỉ với NSAttributedString. Nikolai có cách tiếp cận đúng, sử dụng CTFrameSetters. Tuy nhiên, có thể yêu cầu bộ khung hình hiển thị văn bản trong một khu vực cụ thể (nghĩa là được xác định bởi CGPath).

Bạn sẽ phải tạo 2 khung hình, một cho nắp thả và một cho phần còn lại của văn bản.

Sau đó, bạn lấy khung của nắp thả và tạo CGPathRef chạy quanh không gian của khung của nắp thả.

Sau đó, bạn hiển thị cả hai khung hình vào chế độ xem của mình.

Tôi đã tạo một dự án mẫu với đối tượng có tên DropCapView là lớp con của UIView. Chế độ xem này hiển thị ký tự đầu tiên và bao bọc văn bản còn lại xung quanh nó.

Nó trông như thế này:

dropcap on ios

Có khá một vài bước, vì vậy tôi đã thêm một liên kết đến một dự án github lưu trữ ví dụ. Có ý kiến ​​trong dự án sẽ giúp bạn cùng.

DropCap project on GitHub

Bạn sẽ phải chơi xung quanh với hình dạng của phần tử textBox (ví dụ: các CGPathRef) cho đệm xung quanh các cạnh của quan điểm, và để thắt chặt nó lên đến bức thư thả nắp là tốt.

Dưới đây là can đảm của phương pháp vẽ:

- (void)drawRect:(CGRect)rect { 
    //make sure that all the variables exist and are non-nil 
    NSAssert(_text != nil, @"text is nil"); 
    NSAssert(_textColor != nil, @"textColor is nil"); 
    NSAssert(_fontName != nil, @"fontName is nil"); 
    NSAssert(_dropCapFontSize > 0, @"dropCapFontSize is <= 0"); 
    NSAssert(_textFontSize > 0, @"textFontSize is <=0"); 

    //convert the text aligment from NSTextAligment to CTTextAlignment 
    CTTextAlignment ctTextAlignment = NSTextAlignmentToCTTextAlignment(_textAlignment); 

    //create a paragraph style 
    CTParagraphStyleSetting paragraphStyleSettings[] = { { 
      .spec = kCTParagraphStyleSpecifierAlignment, 
      .valueSize = sizeof ctTextAlignment, 
      .value = &ctTextAlignment 
     } 
    }; 

    CFIndex settingCount = sizeof paragraphStyleSettings/sizeof *paragraphStyleSettings; 
    CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, settingCount); 

    //create two fonts, with the same name but differing font sizes 
    CTFontRef dropCapFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _dropCapFontSize, NULL); 
    CTFontRef textFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _textFontSize, NULL); 

    //create a dictionary of style elements for the drop cap letter 
    NSDictionary *dropCapDict = [NSDictionary dictionaryWithObjectsAndKeys: 
           (__bridge id)dropCapFontRef, kCTFontAttributeName, 
           _textColor.CGColor, kCTForegroundColorAttributeName, 
           style, kCTParagraphStyleAttributeName, 
           @(_dropCapKernValue) , kCTKernAttributeName, 
           nil]; 
    //convert it to a CFDictionaryRef 
    CFDictionaryRef dropCapAttributes = (__bridge CFDictionaryRef)dropCapDict; 

    //create a dictionary of style elements for the main text body 
    NSDictionary *textDict = [NSDictionary dictionaryWithObjectsAndKeys: 
           (__bridge id)textFontRef, kCTFontAttributeName, 
           _textColor.CGColor, kCTForegroundColorAttributeName, 
           style, kCTParagraphStyleAttributeName, 
           nil]; 
    //convert it to a CFDictionaryRef 
    CFDictionaryRef textAttributes = (__bridge CFDictionaryRef)textDict; 

    //clean up, because the dictionaries now have copies 
    CFRelease(dropCapFontRef); 
    CFRelease(textFontRef); 
    CFRelease(style); 

    //create an attributed string for the dropcap 
    CFAttributedStringRef dropCapString = CFAttributedStringCreate(kCFAllocatorDefault, 
                    (__bridge CFStringRef)[_text substringToIndex:1], 
                    dropCapAttributes); 

    //create an attributed string for the text body 
    CFAttributedStringRef textString = CFAttributedStringCreate(kCFAllocatorDefault, 
                   (__bridge CFStringRef)[_text substringFromIndex:1], 
                    textAttributes); 

    //create an frame setter for the dropcap 
    CTFramesetterRef dropCapSetter = CTFramesetterCreateWithAttributedString(dropCapString); 

    //create an frame setter for the dropcap 
    CTFramesetterRef textSetter = CTFramesetterCreateWithAttributedString(textString); 

    //clean up 
    CFRelease(dropCapString); 
    CFRelease(textString); 

    //get the size of the drop cap letter 
    CFRange range; 
    CGSize maxSizeConstraint = CGSizeMake(200.0f, 200.0f); 
    CGSize dropCapSize = CTFramesetterSuggestFrameSizeWithConstraints(dropCapSetter, 
                     CFRangeMake(0, 1), 
                     dropCapAttributes, 
                     maxSizeConstraint, 
                     &range); 

    //create the path that the main body of text will be drawn into 
    //i create the path based on the dropCapSize 
    //adjusting to tighten things up (e.g. the *0.8,done by eye) 
    //to get some padding around the edges of the screen 
    //you could go to +5 (x) and self.frame.size.width -5 (same for height) 
    CGMutablePathRef textBox = CGPathCreateMutable(); 
    CGPathMoveToPoint(textBox, nil, dropCapSize.width, 0); 
    CGPathAddLineToPoint(textBox, nil, dropCapSize.width, dropCapSize.height * 0.8); 
    CGPathAddLineToPoint(textBox, nil, 0, dropCapSize.height * 0.8); 
    CGPathAddLineToPoint(textBox, nil, 0, self.frame.size.height); 
    CGPathAddLineToPoint(textBox, nil, self.frame.size.width, self.frame.size.height); 
    CGPathAddLineToPoint(textBox, nil, self.frame.size.width, 0); 
    CGPathCloseSubpath(textBox); 

    //create a transform which will flip the CGContext into the same orientation as the UIView 
    CGAffineTransform flipTransform = CGAffineTransformIdentity; 
    flipTransform = CGAffineTransformTranslate(flipTransform, 
               0, 
               self.bounds.size.height); 
    flipTransform = CGAffineTransformScale(flipTransform, 1, -1); 

    //invert the path for the text box 
    CGPathRef invertedTextBox = CGPathCreateCopyByTransformingPath(textBox, 
                    &flipTransform); 
    CFRelease(textBox); 

    //create the CTFrame that will hold the main body of text 
    CTFrameRef textFrame = CTFramesetterCreateFrame(textSetter, 
                CFRangeMake(0, 0), 
                invertedTextBox, 
                NULL); 
    CFRelease(invertedTextBox); 
    CFRelease(textSetter); 

    //create the drop cap text box 
    //it is inverted already because we don't have to create an independent cgpathref (like above) 
    CGPathRef dropCapTextBox = CGPathCreateWithRect(CGRectMake(_dropCapKernValue/2.0f, 
                   0, 
                   dropCapSize.width, 
                   dropCapSize.height), 
                &flipTransform); 
    CTFrameRef dropCapFrame = CTFramesetterCreateFrame(dropCapSetter, 
                 CFRangeMake(0, 0), 
                 dropCapTextBox, 
                 NULL); 
    CFRelease(dropCapTextBox); 
    CFRelease(dropCapSetter); 

    //draw the frames into our graphic context 
    CGContextRef gc = UIGraphicsGetCurrentContext(); 
    CGContextSaveGState(gc); { 
     CGContextConcatCTM(gc, flipTransform); 
     CTFrameDraw(dropCapFrame, gc); 
     CTFrameDraw(textFrame, gc); 
    } CGContextRestoreGState(gc); 
    CFRelease(dropCapFrame); 
    CFRelease(textFrame); 
} 

T.B. điều này đi kèm với một số nguồn cảm hứng từ: https://stackoverflow.com/a/9272955/1218605

Các vấn đề liên quan