我想将UILabel
中的文字转换为圆圈(而不是矩形)。
我使用NSLayoutManager
,NSTextContainer
和NSTextStorage
进行了一些实验,但似乎没有效果。
以下示例应该将文本流入40x40的较小矩形(标签为120x120),但似乎没有任何影响。
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:12];
NSTextStorage *ts = [[NSTextStorage alloc] initWithString:multiline.title attributes:@{NSFontAttributeName:font}];
NSLayoutManager *lm = [[NSLayoutManager alloc] init];
NSTextContainer *tc = [[NSTextContainer alloc] initWithSize:CGSizeMake(40, 40)];
[lm addTextContainer:tc];
[ts addLayoutManager:lm];
self.label.attributedText = ts;
Ides的?
答案 0 :(得分:19)
这似乎是一个非常简单的解决方案。 NSTextContainer
具有exclusionPaths
属性。你可以做的是创建两个Bezier路径,定义应该被排除的区域。
所以我这样做了,这是我的方法:
- (void)setCircularExclusionPathWithCenter:(CGPoint)center radius:(CGFloat)radius textView:(UITextView *)textView
{
UIBezierPath *topHalf = [UIBezierPath bezierPath];
[topHalf moveToPoint:CGPointMake(center.x - radius, center.y + radius)];
[topHalf addLineToPoint:CGPointMake(center.x - radius, center.y)];
[topHalf addArcWithCenter:center radius:radius startAngle:M_PI endAngle:0.0f clockwise:NO];
[topHalf addLineToPoint:CGPointMake(center.x + radius, center.y + radius)];
[topHalf closePath];
UIBezierPath *bottomHalf = [UIBezierPath bezierPath];
[bottomHalf moveToPoint:CGPointMake(center.x - radius, center.y - radius)];
[bottomHalf addLineToPoint:CGPointMake(center.x - radius, center.y)];
[bottomHalf addArcWithCenter:center radius:radius startAngle:M_PI endAngle:0 clockwise:YES];
[bottomHalf addLineToPoint:CGPointMake(center.x + radius, center.y - radius)];
[bottomHalf closePath];
textView.textContainer.exclusionPaths = @[bottomHalf, topHalf];
}
使用示例:
[self setCircularExclusionPathWithCenter:CGPointMake(160.0f, 200.0f)
radius:100.0f
textView:_textView];
我的实验结果:
当然你必须使用UITextView而不是UILabel,但我希望它有所帮助:)
答案 1 :(得分:7)
您无法在UILabel中执行此操作,因为它无法访问TextKit堆栈。我所做的是构建我自己的TextKit堆栈和子类NSTextContainer:
-(CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(CGRect *)remainingRect {
CGRect result = [super lineFragmentRectForProposedRect:proposedRect atIndex:characterIndex writingDirection:baseWritingDirection remainingRect:remainingRect];
CGRect r = CGRectMake(0,0,self.size.width,self.size.height);
UIBezierPath* circle = [UIBezierPath bezierPathWithOvalInRect:r];
CGPoint p = result.origin;
while (![circle containsPoint:p]) {
p.x += .1;
result.origin = p;
}
CGFloat w = result.size.width;
p = result.origin;
p.x += w;
while (![circle containsPoint:p]) {
w -= .1;
result.size.width = w;
p = result.origin;
p.x += w;
}
return result;
}
粗暴但有效。看起来像这样:
答案 2 :(得分:2)
使用Swift 4和iOS 11,<div class="field h4">
<label for="inspection_results_soiled">Soiled:</label>
<input id="inspection_results_soiled" type="text" name="inspection[results][soiled]" value="pass" pattern="pass" required><br>
</div>
<div class="field h4">
<label for="inspection_results_contaminated">Contaminated:</label>
<input id="inspection_results_contaminated" type="text" name="inspection[results][contaminated]" value="pass" pattern="pass" required><br>
</div>
有一个名为exclusionPaths
的属性。 NSTextContainer
有以下声明:
路径对象数组,表示文本未显示在文本容器中的区域。
exclusionPaths
此外,var exclusionPaths: [UIBezierPath] { get set }
有一个名为usesEvenOddFillRule
的属性。 UIBezierPath
有以下声明:
一个布尔值,表示奇数绕组规则是否用于绘制路径。
usesEvenOddFillRule
使用var usesEvenOddFillRule: Bool { get set }
,您可以创建围绕圆圈的排除路径,只需几行代码:
usesEvenOddFillRule
以下var exclusionPath: UIBezierPath {
let path = UIBezierPath(ovalIn: bounds)
path.append(UIBezierPath(rect: bounds))
path.usesEvenOddFillRule = true
return path
}
和UITextView
子类显示了如何使用UIViewController
NSTextContainer
和exclusionPaths
UIBezierPath
属性在圆圈内显示文字:< / p>
TextView.swift
usesEvenOddFillRule
import UIKit
class TextView: UITextView {
convenience init() {
self.init(frame: .zero, textContainer: nil)
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
isScrollEnabled = false
isEditable = false
textContainerInset = .zero
self.textContainer.lineBreakMode = .byTruncatingTail
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var exclusionPath: UIBezierPath {
let path = UIBezierPath(ovalIn: bounds)
path.append(UIBezierPath(rect: bounds))
path.usesEvenOddFillRule = true
return path
}
}
ViewController.swift
extension TextView {
// Draw circle
override func draw(_ rect: CGRect) {
UIColor.orange.setFill()
let path = UIBezierPath(ovalIn: rect)
path.fill()
}
// Draw exclusion path
/*
override func draw(_ rect: CGRect) {
UIColor.orange.setFill()
exclusionPath.fill()
}
*/
}
通过选择import UIKit
class ViewController: UIViewController {
let textView = TextView()
override func viewDidLoad() {
super.viewDidLoad()
let string = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, 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. Nam liber te conscient to factor tum poen legum odioque civiuda."
textView.attributedText = NSAttributedString(string: string)
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = textView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let verticalConstraint = textView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let widthConstraint = textView.widthAnchor.constraint(equalToConstant: 240)
let heightConstraint = textView.heightAnchor.constraint(equalToConstant: 240)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textView.textContainer.exclusionPaths = [textView.exclusionPath]
}
}
的一个或另一个实现,您将看到以下显示:
答案 3 :(得分:0)
以下是我对Swift 3中上述问题的贡献。 https://github.com/icatmed/ICRoundLabel.git
import UIKit
import CoreText
@IBDesignable
open class ICRoundLabel: UILabel {
// Switch on/off text rounding, is on by default
@IBInspectable open dynamic var isRounded:Bool = true {
didSet{
setNeedsDisplay()
}
}
// Specify text alignment
@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'roundedTextAlignment' instead.")
@IBInspectable open dynamic var alignment:UInt8 {
set{
self.roundedTextAlignment = CTTextAlignment(rawValue: newValue)!
setNeedsDisplay()
}
get{
return roundedTextAlignment.rawValue
}
}
// Font scale
@IBInspectable open dynamic var fillTextInCenter:Bool = true {
didSet{
setNeedsDisplay()
}
}
// Font step
@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'internalFontStep' instead.")
@IBInspectable open dynamic var fontStep:CGFloat {
set(newValue) {
internalFontStep = max(newValue, 0.1)
}
get {
return internalFontStep
}
}
open var roundedTextAlignment:CTTextAlignment = .center
open var internalFontStep:CGFloat = 1
override open func drawText(in rect: CGRect) {
// Check if custom text draw is needed
if !isRounded {
super.drawText(in: rect)
return
}
// Check if text exists
guard let text = self.text else {
return
}
if text == "" {
return
}
// Get graphics context
guard let context = UIGraphicsGetCurrentContext() else {
return
}
//MARK: Create attributed string
var stringRange = NSMakeRange(0, text.characters.count)
let attrString = CFAttributedStringCreate(kCFAllocatorDefault, text as CFString!, attributedText?.attributes(at: 0, effectiveRange: &stringRange) as CFDictionary!)
let attributedString = CFAttributedStringCreateMutableCopy(kCFAllocatorDefault, CFIndex.max, attrString)!
let stringLength = CFAttributedStringGetLength(attributedString)
// Set a paragraph style
let cfStringRange = CFRangeMake(0, stringLength)
let settings = [CTParagraphStyleSetting(spec: .alignment, valueSize: MemoryLayout.size(ofValue: roundedTextAlignment), value: &roundedTextAlignment)]
let paragraphStyle = CTParagraphStyleCreate(settings, 1)
CFAttributedStringSetAttribute(attributedString, cfStringRange, kCTParagraphStyleAttributeName, paragraphStyle)
// Make custom transitions with context
context.translateBy(x: 0.0, y: frame.size.height)
context.scaleBy(x: 1.0, y: -1.0)
// New drawing rect with insets
let drawingRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: rect.size.width, height: rect.size.height))
// Align text in center
var boundingBox = text.boundingRect(with: drawingRect.size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
//MARK: Create elliptical path
var path = CGPath(roundedRect: drawingRect, cornerWidth: drawingRect.width/2, cornerHeight: drawingRect.height/2, transform: nil)
//MARK: Frame and range calculation nested function
func getTextFrameRange() -> (CTFrame, CFRange) {
let textFrame = CTFramesetterCreateFrame(CTFramesetterCreateWithAttributedString(attributedString), cfStringRange, path, nil)
let rangeThatFits = CTFrameGetVisibleStringRange(textFrame)
return (textFrame, rangeThatFits)
}
var textFrame:CTFrame
var rangeThatFits:CFRange
//MARK: Scaling font size if needed
if fillTextInCenter {
var fontSize = font.pointSize
var estimatedFont = font.withSize(fontSize)
// Pin text in center of initial rect
var boxHeight = ceil(boundingBox.height)
func updateBoundingBox() {
boundingBox.origin = CGPoint(x: ceil((drawingRect.size.height - boxHeight)/2), y: ceil((drawingRect.size.height - boxHeight)/2))
boundingBox.size = CGSize(width: boxHeight, height: boxHeight)
}
path = CGPath(roundedRect: boundingBox, cornerWidth: boundingBox.width/2, cornerHeight: boundingBox.height/2, transform: nil)
(_, rangeThatFits) = getTextFrameRange()
updateBoundingBox()
// Fit text in center
while cfStringRange.length != rangeThatFits.length {
// Increase size of bounding box size if needed
// or decrease font size
if boundingBox.width < drawingRect.width {
boxHeight += 1
//Update bounding box accoringly to new box size
updateBoundingBox()
path = CGPath(roundedRect: boundingBox, cornerWidth: boundingBox.width/2, cornerHeight: boundingBox.height/2, transform: nil)
(_, rangeThatFits) = getTextFrameRange()
continue
} else {
CFAttributedStringSetAttribute(attributedString, cfStringRange, kCTFontAttributeName, estimatedFont)
(_, rangeThatFits) = getTextFrameRange()
// Increase or decrease font size
fontSize += cfStringRange.length < rangeThatFits.length ? internalFontStep : -internalFontStep
estimatedFont = font.withSize(fontSize)
}
}
}
//MARK: Draw the text frame in the view's graphics context
(textFrame, _) = getTextFrameRange()
CTFrameDraw(textFrame, context)
}
@IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
layer.borderColor = borderColor.cgColor
}
}
@IBInspectable var borderWidth: CGFloat = 1.0 {
didSet {
layer.borderWidth = borderWidth
}
}
override open func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 0.5 * bounds.size.width
clipsToBounds = true
}
}