實現(xiàn)了外描邊效果之后,產(chǎn)品問內(nèi)描邊好做嗎,我說簡單!
簡單個蛋!

因為蘋果本身的描邊效果,是雙向描邊,不是單側(cè)的,見上一篇,而且發(fā)現(xiàn)也無法用外描邊的方式實現(xiàn).
想了很久,最后想的實現(xiàn)方式是,拿到每一個字符的path,然后用字體本身的大小畫出來,再用比本身小一點的筆刷,在上面疊加一層細(xì)一些的文字,間接實現(xiàn)內(nèi)描邊的效果.
理論是沒問題的
然后用coretext,拿到ctframe,再用ctFrame獲取到每一行的文字CTline,再用CTLine獲取到CTRun(一段相同富文本的合集),再用CTrun獲取到每一個字的字形CGGlyph.
代碼如下
let letters = CGMutablePath.init()
let framesetterNew = CTFramesetterCreateWithAttributedString(attributeString)
let frameNew = CTFramesetterCreateFrame(framesetterNew, CFRangeMake(0, CFAttributedStringGetLength(attributeString)), path, nil)
let lineArr = CTFrameGetLines(frameNew)
let lineCount = CFArrayGetCount(lineArr)
let _lineOrigins = UnsafeMutablePointer<CGPoint>.allocate(capacity: MemoryLayout<CGPoint>.size * lineCount)
CTFrameGetLineOrigins(frameNew, CFRange(location: 0, length: 0), _lineOrigins)
for lineIndex in 0 ..< lineCount{
let linePointer = CFArrayGetValueAtIndex(lineArr, lineIndex)
let line = unsafeBitCast(linePointer, to: CTLine.self)
let runArray = CTLineGetGlyphRuns(line)
for index in 0 ..< CFArrayGetCount(runArray) {
let runPointer = CFArrayGetValueAtIndex(runArray, index)
let run = unsafeBitCast(runPointer, to: CTRun.self)
let runFont = unsafeBitCast(CFDictionaryGetValue(CTRunGetAttributes(run),unsafeBitCast(kCTFontAttributeName, to: UnsafePointer.self)),to: CTFont.self)
for glyphIndex in 0 ..< CTRunGetGlyphCount(run){
let thisGlyphRange = CFRangeMake(glyphIndex, 1)
let glyphPointer = UnsafeMutablePointer<CGGlyph>.allocate(capacity: MemoryLayout<CGGlyph>.size)
let position = UnsafeMutablePointer<CGPoint>.allocate(capacity: MemoryLayout<CGPoint>.size)
CTRunGetGlyphs(run, thisGlyphRange, glyphPointer)
CTRunGetPositions(run, thisGlyphRange, position)
let letterPath = CTFontCreatePathForGlyph(runFont, glyphPointer.pointee, nil)
let transT = CGAffineTransform.init(translationX: _lineOrigins[lineIndex].x + position.pointee.x, y: position.pointee.y + _lineOrigins[lineIndex].y)
if letterPath != nil{
letters.addPath(letterPath!, transform: transT)
}
free(glyphPointer)
free(position)
}
}
}
ctx.saveGState()
ctx.addPath(letters)
ctx.setStrokeColor(UIColor.blue.cgColor)//無用顏色
let color = attributeString.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor
ctx.setFillColor(color!.cgColor) //設(shè)置字體本身顏色
ctx.closePath()
ctx.fillPath()
ctx.addPath(letters)
print(abs(strokeWidth))
ctx.setLineWidth(abs(strokeWidth))
ctx.setBlendMode(CGBlendMode.clear)
ctx.strokePath()
ctx.setBlendMode(CGBlendMode.destinationAtop)
var newAtt = NSAttributedString.init(attributedString: attributeString)
newAtt = newAtt.appendAddAttributes([NSAttributedString.Key.foregroundColor:self.strokeColor])
let framesettera = CTFramesetterCreateWithAttributedString(newAtt)
let framea = CTFramesetterCreateFrame(framesettera, CFRangeMake(0, CFAttributedStringGetLength(newAtt)), path, nil)
CTFrameDraw(framea, ctx)
ctx.restoreGState()
這樣就拿到了所有文字的path!搞定!

運行效果如下,跟想的完全不一樣

瞬間我就慌了,我可是看了整整一天的coretext。能不能用拿到的這個東西搞事情呢,畢竟花了一整天時間,這個時候,就顯示出智慧的重要性了.曲線救國
首先將獲取到的path用fill模式畫一遍,這就是原本的文字
ctx.saveGState() //先保存context的狀態(tài),用于畫完描邊之后恢復(fù)
ctx.addPath(letters)
ctx.setFillColor(color!.cgColor) //設(shè)置字體本身顏色
ctx.closePath()
ctx.fillPath()
然后重點來了,再畫一遍剛才鏤空的那個path,然后用粗線條畫,

這樣和原本的畫好的文字做疊加,選擇clear模式,這樣兩者重疊的部分顏色就會被清理掉,代碼
ctx.addPath(letters)
print(abs(strokeWidth))
ctx.setLineWidth(abs(10))
ctx.setBlendMode(CGBlendMode.clear)
ctx.strokePath()
效果如下

這樣就得到了,內(nèi)描邊效果里面顯示的內(nèi)容
我真他娘的是個天才
原本想的是得到的這個瘦弱的圖形畫在,本身原本的字體上,就完成效果了
然后坑又來了
clearmode,會把之前所有已經(jīng)畫好的顏色,清除掉,這樣無論最下層原本的文字什么顏色,都會被裁剪到,第二部瘦弱字體的那個大小。這一點我忘得死死地

后來想了兩個方案,一個是新建一個context,在這個context上面畫圖,然后再把這個context push進(jìn)棧,然后;兩者混合
另外一個是將coretext得到的path做一個translate弄到別處畫,畫好了再clip掉圖形,再放到原本的位置,這樣也不受clear影響.
但是兩個都只是猜測.不一定成.
后來,想了想有沒有,將圖層上下顛倒顯示的疊加模式呢,就是原本綠色在下面,紅色在上面,混合后紅色在下面,綠色在上面?
還真他娘的有
CGBlendMode.destinationAtop
這樣我先clear模式得到需要的瘦弱圖形,然后再畫原本文字蓋在上面,本來的效果是原本的文字完全的蓋住了瘦小的那個文字.然后用這個疊加模式顏色顛倒后,效果就是內(nèi)描邊效果了.
真費勁吶.網(wǎng)上還沒有搜到先例.
上代碼
ctx.saveGState()
ctx.addPath(letters)
ctx.setStrokeColor(UIColor.blue.cgColor)//無用顏色
let color = attributeString.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor
ctx.setFillColor(color!.cgColor) //設(shè)置字體本身顏色
ctx.closePath()
ctx.fillPath()
ctx.addPath(letters)
print(abs(strokeWidth))
ctx.setLineWidth(abs(strokeWidth))
ctx.setBlendMode(CGBlendMode.clear)
ctx.strokePath()
ctx.setBlendMode(CGBlendMode.destinationAtop)
var newAtt = NSAttributedString.init(attributedString: attributeString)
newAtt = newAtt.appendAddAttributes([NSAttributedString.Key.foregroundColor:self.strokeColor])
let framesettera = CTFramesetterCreateWithAttributedString(newAtt)
let framea = CTFramesetterCreateFrame(framesettera, CFRangeMake(0, CFAttributedStringGetLength(newAtt)), path, nil)
CTFrameDraw(framea, ctx)
ctx.restoreGState()
最終效果

外描邊


另一種實現(xiàn)方式,得到字形path后clip,這樣超出path區(qū)域的地方就會被裁掉,再stroke的時候直接就是內(nèi)描邊效果了
辦法總比困難多