2012-04-13 13 views
1

これは生産に寄与したので、結果を投稿するのは公平と思われます)。 iOSとCore Textで属性付きの文字列を使用してスクロールする例を探している場合は、ここでそれらを組み立てます。RE:オートローテーション、スクロール、箇条書き、インデント付きのUIScrollView(帰属文字列付き)

いくつかの余分なポイント:

ラッピング:

これは属性付き文字列のlineBreakMode、ラッピングにはデフォルトで行われます。文字列からソフトの改行を取得することはできません(NSCextViewが文字列に '\ n'文字を置くココアとは異なります)。しかし、あなたはする必要はありません。コアテキストのフレームセッターを使用して、指定されたフレーム内の文字列の高さを指定することができます。

CATextLayer:

それはで動作するように便利です。属性付きの文字列を割り当ててラップして描画するだけです。あなたが選択したフォントで表示する短い文字列を持っているなら、それは素晴らしいです。しかし、CATextLayerは先頭や字下げを認識しないので、このプロジェクトではCALayerに直接描画しました。

フォント:

それがiOS版で属性付きの文字列を構築するために必要とされているので、私は、ほとんどの部分はCTFontRefで立ち往生し、それは正確なリーディングを与えますが、それはUIFontで動作するように簡単です場合があります。ここでUIFontにCTFontRef(fontr)を変換する方法です:

NSString *sFontName = (NSString *)CTFontCopyName(fontr, kCTFontPostScriptNameKey); 
UIFont *font = [UIFont fontWithName:sFontName size:CTFontGetSize(fontr)]; 
CGSize sizeString = [string sizeWithFont:font]; 
[sFontName release]; 

あなたがゼロからUIFontを作成する場合は、フォント名の家族の一部にスペースを受け入れる、またはあなたがそれらを破ることがあるので注意してください一緒に、しかし「太字」ハイフンで区切られている必要があります

self.fontTickerNormal = [UIFont fontWithName:@"Helvetica Neue" 
             size:18.0f]; // OK 
self.fontTickerIndent = [UIFont fontWithName:@"HelveticaNeue" 
             size:16.0f]; // OK too 
self.fontTickerBold = [UIFont fontWithName:@"HelveticaNeue-Bold" 
             size:18.0f]; // both the smash and the hyphen are required 

CTFontRefは、より簡単です。すべての名前部分はスペースで区切ることができます。以下では正常に動作します:

self.fontrTickerBold = CTFontCreateWithName((CFStringRef)@"Helvetica Neue Bold", 18.0f, NULL); 

回答-例

答えて

2

は、コンポーネントの準備...従うこと:彼らは一箇所(すなわち内の文字列とその属性のすべてを組み立てるため ほとんどの例が読みやすくなりますclear example)が、あなたはすべてのコンポーネント上にハングアップしない場合、ビューは、文字列を使用する場合、のUIViewControllerののviewDidLoadですべてを設定することが最善と思われるので、あなたは、悪いアクセスの例外を取得します:

- (void)viewDidLoad { 
[super viewDidLoad]; 

// FONTS ETC. 

// Prepare fonts for the ticker display. 
self.fontrTickerNormal = CTFontCreateWithName((CFStringRef)@"Helvetica Neue", 18.0f, NULL); 
self.fontrTickerBold = CTFontCreateWithName((CFStringRef)@"Helvetica Neue Bold", 18.0f, NULL); 
self.fontrTickerIndent = CTFontCreateWithName((CFStringRef)@"Helvetica Neue", 16.0f, NULL); 
// Determine width of bullet sub-indent 
// (Use a UIFont and UIKit's sizeWithFont. No need to fuss with framesetter and leading here; all we need is width.) 
UIFont *fontTickerIndent_uif = [UIFont fontWithName:@"Helvetica Neue" size:16.0f]; 
NSString *stringWithBullet = [NSString stringWithUTF8String:"\u2022"]; // bullet char 
NSString *stringWithBulletPlusSpace = [stringWithBullet stringByAppendingString:@" "]; 
CGSize sizeBulletPlusSpace = [stringWithBulletPlusSpace sizeWithFont:fontTickerIndent_uif]; 
self.fWidthBulletPlusSpace = sizeBulletPlusSpace.width; 
// Prepare bulleted string to which text can be appended 
NSString *stringBulletBuild = [@"\n" stringByAppendingString:stringWithBullet]; 
stringBulletBuild = [stringBulletBuild stringByAppendingString:@"\t"]; 
self.sBulletedLineBreak = stringBulletBuild; 

// Initialize the collections that will hold onto the individual strings and dicts via alloc, as opposed to an autoreleased method, so that we can control the order in which they are released in viewDidUnload. (If dicts were to be released before the strings that use them, there would be a bad-access crash): 
NSMutableArray *asmarrTickerStringsNew = [[NSMutableArray alloc] initWithCapacity:20]; // guess 
self.asmarrTickerStrings = asmarrTickerStringsNew; 
[asmarrTickerStringsNew release]; 
NSMutableArray *dmarrTickerAttributesNew = [[NSMutableArray alloc] initWithCapacity:20]; // one per string 
self.dmarrTickerAttributes = dmarrTickerAttributesNew; 
[dmarrTickerAttributesNew release]; 
// Don't initialize the cumulative-string ppty (mattrStgr) here. There is no way of appending to an empty CFMutableAttributedStringRef, so you have to wait till the first string goes through addProgressTickerLine. 

// STYLE SETTINGS: 

// paragStyleNormal: 
CFIndex countSettings = 3; // normal and bold styles both have 3 settings 
// - left alignment (the default, but just in case) 
CTTextAlignment alignment = kCTLeftTextAlignment; 
CTParagraphStyleSetting styleAlignLeft = { kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment }; 
// - wrapping (again the default, but just in case) 
CTLineBreakMode wrapping = kCTLineBreakByWordWrapping; 
CTParagraphStyleSetting styleWrapped = { kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &wrapping }; 
// - normal leading (so framesetter calculates the correct height) 
CGFloat fLeadingNormal = CTFontGetLeading(self.fontrTickerNormal); 
CTParagraphStyleSetting styleLeadingNormal = { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &fLeadingNormal }; 
CTParagraphStyleSetting arrSettingsNormal[] = { styleAlignLeft, styleWrapped, styleLeadingNormal }; 
self.paragStyleNormal = CTParagraphStyleCreate(arrSettingsNormal, countSettings); 
// paragStyleBold: 
// - bold leading (otherwise this style is same as normal) 
CGFloat fLeadingBold = CTFontGetLeading(self.fontrTickerBold); 
CTParagraphStyleSetting styleLeadingBold = { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &fLeadingBold }; 
CTParagraphStyleSetting arrSettingsBold[] = { styleAlignLeft, styleWrapped, styleLeadingBold }; 
self.paragStyleBold = CTParagraphStyleCreate(arrSettingsBold, countSettings); 
// paragStyleIndent: 
// - indented leading (font is smaller) 
CGFloat fLeadingIndent = CTFontGetLeading(self.fontrTickerIndent); 
CTParagraphStyleSetting styleLeadingIndent = { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &fLeadingIndent }; 
// - Indent the first line up to the bullet. 
CGFloat indentFirstLineHead = 15.0f; 
CTParagraphStyleSetting styleFirstLineHeadIndent = { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &indentFirstLineHead }; 
// - Add a tab stop to allow for the bullet and some following space. 
self.tabStop = CTTextTabCreate(alignment, (double)self.fWidthBulletPlusSpace, NULL); 
CTTextTabRef tabStopPtr[] = { self.tabStop }; 
self.cfarrTabStop = CFArrayCreate(NULL, (const void **) tabStopPtr, 1, NULL); 
CTParagraphStyleSetting styleTabStops = { kCTParagraphStyleSpecifierTabStops, sizeof(CTTextTabRef), &cfarrTabStop }; 
// - Indent the wrapped lines to the same point as the tab stop. 
CGFloat indentHead = indentFirstLineHead + self.fWidthBulletPlusSpace; 
CTParagraphStyleSetting styleHeadIndent = { kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &indentHead }; 
// Put paragStyleIndent together. 
countSettings += 3; // 3 style settings added (firstLineHeadIndent, tabStop, and HeadIndent) 
CTParagraphStyleSetting arrSettingsIndent[] = { styleAlignLeft, styleWrapped, styleLeadingIndent, styleFirstLineHeadIndent, styleTabStops, styleHeadIndent }; 
self.paragStyleIndent = CTParagraphStyleCreate(arrSettingsIndent, countSettings); 

// LAYER AND SCROLLVIEW 

// Set up the layer for the progress ticker. 
CGSize sizeTicker = self.svProgressTicker.layer.bounds.size; 
// Frame the layer: 
/* 
- Don't add any padding to the X origin; the scrollview apparently figures some into its bounds automatically. 
- Do add some padding to the Y origin; otherwise the first string will hug the top. 
- Decrease the width to allow for the scrollbar, which the scrollview bounds do not account for. 
- Height is 0 for now, since it doesn't have any strings yet. 
*/ 
CGRect rectTickerInset = CGRectMake(0.0f, kMargin, sizeTicker.width - kMargin, 0.0f); 
CALayer *layerTicker = [CALayer layer]; 
[layerTicker setFrame:rectTickerInset]; 
// Assign this controller as the delegate and add it to the scrollview's root layer. 
// (See headnote to the drawLayer method.) 
[layerTicker setDelegate:self]; 
self.layProgressTicker = layerTicker; 
[svProgressTicker.layer addSublayer:self.layProgressTicker]; 
// Set the scrollView's contentSize per the layer's frame -- which will become taller with each appended string until it exceeds the scrollView's bounds. 
[self.svProgressTicker setContentSize:CGSizeMake(rectTickerInset.size.width, rectTickerInset.size.height)]; 
[self.svProgressTicker setClipsToBounds:YES]; // otherwise the contents will overflow the border. 

} 

文字列を追加します。

(これらの定数を使用して:)

#define kTickerStyleNormal   0 
#define kTickerStyleIndent   1 
#define kTickerStyleBold    2 
#define kTickerStyleFirstLine  3 

/** 
Appends the string arg to the Progress Ticker scrollView, styled per uiStyle. 
Note: String arg should NOT have any line breaks. 
*/ 
- (void) addProgressTickerLine:(NSString *)string 
         inStyle:(uint8_t)uiStyle { 

// Determine the font. 
CTFontRef fontr = nil; 
switch (uiStyle) { 
    case kTickerStyleNormal: 
     fontr = self.fontrTickerNormal; 
     break; 
    case kTickerStyleIndent: 
     fontr = self.fontrTickerIndent; 
     break; 
    case kTickerStyleBold: 
     fontr = self.fontrTickerBold; 
     break; 
    case kTickerStyleFirstLine: 
     fontr = self.fontrTickerBold; 
     break; 
    default: 
     fontr = self.fontrTickerNormal; 
     break; 
} 

// Prepare the paragraph style (preassembled in viewWillAppear), to govern inter-line height and indentation. 
// At the same time, add the initial line break. 
CTParagraphStyleRef paragStyle = NULL; 
switch (uiStyle) { 
    case kTickerStyleNormal: 
     string = [@"\n" stringByAppendingString:string]; 
     paragStyle = self.paragStyleNormal; 
     break; 
    case kTickerStyleBold: 
     // For main un-indented lines, simply add a line break. 
     string = [@"\n" stringByAppendingString:string]; 
     paragStyle = self.paragStyleBold; 
     break; 
    case kTickerStyleFirstLine: { 
     paragStyle = self.paragStyleBold; 
     // No line break for the first line. 
     // (Q: Why not avoid this case by putting line break at the end, rather than the beginning?) 
     // (A: The indented string would then require 2 steps to sandwich it between the bullet-tab and the line break.) 
     break; 
    } 
    case kTickerStyleIndent: { 
     // For indented bullet lines, prepend lineBreak-bullet-tab to the string. 
     string = [self.sBulletedLineBreak stringByAppendingString:string]; 
     // Assign the indented paragStyle. 
     paragStyle = self.paragStyleIndent; 
     break; 
    } 
    default: // just in case 
     paragStyle = self.paragStyleNormal; 
     break; 
} 

// Determine the text (foreground) color per... 
UIColor *colorHSV = [UIColor blueColor]; // switch-case omitted 

// PUT IT ALL TOGETHER. 

// Combine the above into a dictionary of attributes. 
CFStringRef keys[] = { kCTFontAttributeName, kCTParagraphStyleAttributeName, kCTForegroundColorAttributeName }; 
CFTypeRef values[] = { fontr, paragStyleIndent, colorHSV.CGColor }; 
CFDictionaryRef dictr = CFDictionaryCreate(NULL, 
              (const void **)&keys, 
              (const void **)&values, 
              sizeof(keys)/sizeof(keys[0]), 
              &kCFTypeDictionaryKeyCallBacks, 
              &kCFTypeDictionaryValueCallBacks); 
[self.dmarrTickerAttributes addObject:(id)dictr]; // provides for release 

// Use the attributes dictionary to make an attributed string out of the plain string. 
CFAttributedStringRef attrStgr = CFAttributedStringCreate(kCFAllocatorDefault, (CFStringRef)string, dictr);  
[self.asmarrTickerStrings addObject:(id)attrStgr]; // provides for release 
// Adding an object to a collection increments its retain count, so attrStgr are now at count 2 and should be decremented to 1; they will be brought to 0 upon release of the collections. 
CFRelease(attrStgr); 
CFRelease(dictr); 

// If cumulative attrStg is nil, initialize it with a maxLength of 0 to indicate that it is unlimited. 
if (!self.mattrStgr) 
    self.mattrStgr = CFAttributedStringCreateMutableCopy(kCFAllocatorDefault, 0, attrStgr); 
// Else append the newly completed attrStg, using the method suggested in the CFMutableAttributedString class ref overview. 
else { 
    CFRange rangeAppend = CFRangeMake(CFAttributedStringGetLength(self.mattrStgr), 0); 
    CFAttributedStringReplaceAttributedString(self.mattrStgr, rangeAppend, attrStgr); 
} 

// DISPLAY IT. 

// Now that the new string has been appended, call the method that will adjust the contentSize, trigger the draw method, and scroll to the end. 
[self adjustProgressTickerFrame]; 

// Do NOT release attrstg or any of its styling components here. They are needed cumulative string property is voided. They have been propertized so that they can be released in dealloc (here in viewDidUnload). 

} 

、高さを調節しdrawLayerを呼び出して、一番下までスクロール:

/** 
Adjust the height of the scrollView and its content layer to accommodate newly appended strings and/or orientation change, redraw the layer, and scroll to the bottom. 
Called at the end of addProgressTickerLine and upon willAnimateRotationToInterfaceOrientation. 
*/ 
- (void) adjustProgressTickerFrame { 

// Get the width within which the text has to fit in the view's current frame. 
CGSize sizeTickerView = self.svProgressTicker.layer.bounds.size; 
CGFloat fWidthInset = sizeTickerView.width - kMargin; 

// Make a framesetter in order to get the size of the cumulative string. 
/* 
Note re Leading: 
- The framesetter will suggest too short a height unless the attributed string has paragraphStyle attributes that specify the leading. 
- But if you attempt to do an efficient setNeedsDisplay:InRect routine, drawing only the last-appended string per its individual height, framesetter will report the the hard line breaks as a full line, not just as leading, so you'll end up with dimensions that are much too tall. There's no choice but to get the full size and draw the full cumulative string each time. 
*/ 
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(self.mattrStgr); 
CFIndex length = CFAttributedStringGetLength(self.mattrStgr); 
CFRange textRange = CFRangeMake(0, length); 
CFRange fitRange; 
CGSize sizeString = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, textRange, NULL, CGSizeMake(fWidthInset, CGFLOAT_MAX), &fitRange); 

// Round the reported height up. Dimensions are reported as very precise fractions, but framing and drawing is by whole pixels. 
CGFloat fHeight = ceilf(sizeString.height); 

// Reframe the layer. 
CGRect rectTickerInset = CGRectMake(0.0f, kMargin, sizeTickerView.width - kMargin, fHeight); 
[self.layProgressTicker setFrame:rectTickerInset]; 

// Adjust the scrollView's contentSize. 
// (It should be slightly taller than the layer, to add padding at top and bottom.) 
[self.svProgressTicker setContentSize:CGSizeMake(fWidthInset, (fHeight + (kMargin*2)))]; 

// Redraw the entire layer. 
[self.layProgressTicker setNeedsDisplay]; 

// If the layer is now taller than the scrollView's bounds, scroll down. 
// Do NOT use animation. 
// (If you do when the view first appears, it won't scroll down far enough, should the view be assigned lots of string content initially. (Presumably the initial autorotation anim prevents this one from being completed.) And afterwards, it doesn't make any difference; there's a spring action that happens regardless.) 
CGFloat fHeightExcess = fHeight - (sizeTickerView.height - (kMargin*2)); 
if (fHeightExcess > 1.0f) 
    [self.svProgressTicker setContentOffset:CGPointMake(0.0f, fHeightExcess) animated:NO]; 

} 

ドロー:

/** 
CALayer's delegate method for drawing its content, triggered by calling setNeedsDisplay on the layer. 

This controller is assigned as the layer's delegate, and calls setNeedsDisplay when: 
1) a string is appended 
2) the device is rotated into a different aspect ratio 

Q: Why not subclass UIScrollView and have IT be the delegate? After all, the class ref says the view associated with the layer must be the delegate. 
A1: Apparently "associated" in Apple-speak means as in view to its ROOT layer. Sublayers can have any delegate you choose. 
A2: If scrollView is the delegate and does the content drawing, the app will crash. ScrollView probably issues setNeedsDisplay:inRect: gazillions of times as it scrolls, overwhelming the processor. 
*/ 
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { 

// Get the rect of the entire area available for drawing. 
// (We cannot use the layer's frame rect directly because it has origin insets; they would be doubled if used here too.) 
CGRect drawingRect = CGRectMake(0.0f, 0.0f, self.layProgressTicker.frame.size.width, self.layProgressTicker.frame.size.height); 

// Turn the context upside down to match the layer's orientation. 
// (The context origin is at the lower left, whereas the layer origin is at the upper left.) 
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity); 
CGContextTranslateCTM(ctx, drawingRect.origin.x, drawingRect.size.height); 
CGContextScaleCTM(ctx, 1, -1); 

// Make a rectangular path in which to draw. 
CGMutablePathRef path = CGPathCreateMutable(); 
CGPathAddRect(path, NULL, drawingRect); 

// Use Core Text's framesetter to frame the cumulative (mutable) attributed string inside the rectangular path. 
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(self.mattrStgr); 
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);  
CFRelease(framesetter); // ok to release here; it's not part of "frame" 
CFRelease(path); 

// Draw the framesetter frame. 
CTFrameDraw(frame, ctx); 
CFRelease(frame); 

} 

オートローテーション:ここではすべてこのようなものを(保持性質ものdeallocでリリースされている)アンロードする方法は次のとおりです:私はこの権利を持っていると思います

/* 
Autorotation methods are called in this order: 
    1) shouldAutorotate 
    2) willRotate 
    3) willAnimate 
    4) didRotate 
*/ 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { 
// Return YES to support all 4 orientations. 
return YES; 
} 

- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { 
// "When this method is called, the interfaceOrientation property still contains the view’s original orientation." (So hang onto it.) 
// You can't wait until willAnimate because, by then, that property will have been set to the new orientation. 
// Which leaves a mystery: The didRotateFromInterfaceOrientation is called AFTER willAnimate, and at that point the view controller still is able to send in the fromInterfaceOrientation arg. 
self.bFromPortrait = UIInterfaceOrientationIsPortrait([self interfaceOrientation]);  
} 

- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { 

// If rotating from portrait to landscape or vice versa, the text layers need adjustment. 
BOOL bToPortrait = UIInterfaceOrientationIsPortrait(toInterfaceOrientation); 

if (self.bFromPortrait != bToPortrait) 
    [self adjustProgressTickerFrame]; 

} 

- (void)viewDidUnload { 

// It's unlikely that this view will go away and then come back, so viewDidLoad assumes all properties are null -- therefore nullify/release EVERYTHING here. 

self.svProgressTicker = nil; 
self.layProgressTicker = nil; 
self.vSyncInstructions = nil; 
self.tlSyncInstructions = nil; 
self.btnSyncEtc = nil; 

// Release the cumulative attributed string BEFORE releasing any of its component strings. 
CFRelease(self.mattrStgr); 
// Nullifying the retained collections will release them -- and their contents, whose retainCounts were incremented upon addition to the collection. So it's important to nullify the strings collection first, then the attr dicts. 
self.asmarrTickerStrings = nil; 
self.dmarrTickerAttributes = nil; 
// Now we can release the "created" settings, then their components. 
CFRelease(self.paragStyleNormal); 
CFRelease(self.paragStyleBold); 
CFRelease(self.paragStyleIndent); 
CFRelease(self.tabStop); 
CFRelease(self.cfarrTabStop); 
CFRelease(self.fontrTickerNormal); 
CFRelease(self.fontrTickerBold); 
CFRelease(self.fontrTickerIndent); 
CFRelease(self.fontrInstructions); 
self.fontInstructionsBenchmark = nil; 

[super viewDidUnload]; 

} 

portrait scrolled downportrait scrolled up landscape scrolled downlandscape scrolled up

0

ここでは、スウィフト

let label = UILabel() 
label.frame = CGRect(x: 40, y: 100, width: 280, height: 600) 
label.textColor = UIColor.lightGray 
label.numberOfLines = 0 

let arrayString = [ 
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 
    "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 
    "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", 
    "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 
] 

label.attributedText = add(stringList: arrayString, font: label.font, bullet: "") 

self.view.addSubview(label) 

との素敵なソリューションが弾丸を追加している。ここ

func add(stringList: [String], 
     font: UIFont, 
     bullet: String = "\u{2022}", 
     indentation: CGFloat = 20, 
     lineSpacing: CGFloat = 2, 
     paragraphSpacing: CGFloat = 12, 
     textColor: UIColor = .gray, 
     bulletColor: UIColor = .blue) -> NSAttributedString { 

    let textAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: textColor] 
    let bulletAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: bulletColor] 

    let paragraphStyle = NSMutableParagraphStyle() 
    let nonOptions = [NSTextTab.OptionKey: Any]() 
    paragraphStyle.tabStops = [ 
     NSTextTab(textAlignment: .left, location: indentation, options: nonOptions)] 
    paragraphStyle.defaultTabInterval = indentation 
    //paragraphStyle.firstLineHeadIndent = 0 
    //paragraphStyle.headIndent = 20 
    //paragraphStyle.tailIndent = 1 
    paragraphStyle.lineSpacing = lineSpacing 
    paragraphStyle.paragraphSpacing = paragraphSpacing 
    paragraphStyle.headIndent = indentation 

    let bulletList = NSMutableAttributedString() 
    for string in stringList { 
     let formattedString = "\(bullet)\t\(string)\n" 
     let attributedString = NSMutableAttributedString(string: formattedString) 

     attributedString.addAttributes(
      [NSAttributedStringKey.paragraphStyle : paragraphStyle], 
      range: NSMakeRange(0, attributedString.length)) 

     attributedString.addAttributes(
      textAttributes, 
      range: NSMakeRange(0, attributedString.length)) 

     let string:NSString = NSString(string: formattedString) 
     let rangeForBullet:NSRange = string.range(of: bullet) 
     attributedString.addAttributes(bulletAttributes, range: rangeForBullet) 
     bulletList.append(attributedString) 
    } 

    return bulletList 
} 

属性結果である:

enter image description here

関連する問題