沿弧定位 CATextLayers
Position CATextLayers along Arc
我正在尝试构建一个自定义控件,它可能看起来像这个 Inkscape 模型:
我在用 drawRect()
做事,但我需要为一些元素设置动画,所以我决定切换到 CALayer
的合成。我一直在使用 CGContextRef
函数绘制文本,但是为每个字符切换到 CATextLayer
后,我似乎无法正确转换它们。
我的一般方法是为每个角色创建一个 CATextLayer
。我认为我可以使用每一层的preferredFrameSize()
来获得角色的最终大小。然后我想我会在我的控件的 layoutSubviews()
方法中调整它们的位置和旋转。
"What have I tried?" 感觉自己一直坐在sit-n-spin上,蒙着眼睛,扔飞镖。你说出来,我试过了。
我可以通过以下方式确定每个角色的旋转角度:
let baseline = (self.ringBox.width * 3 / 8) // radius out to the baseline arc
let circumference = baseline * Tau
var advance = wordWidth.half.negated // wordWidth was summed from the width of each character earlier
let angle = 0.75.rotations
for each in self.autoCharacters {
let charSize = each.bounds.size
advance += charSize.width.half
let charRotation = (advance / circumference - 0.75).rotations + angle
...
}
我注意到 layoutSubviews()
似乎被调用了两次。为什么?我注意到第二次通过时,preferredFrameSize()
似乎变细了。为什么?是不是因为我设置了 affineTransform
并且它具有累积效应。我试图最初将层的位置设置为父框的中心,以及边界为 preferredFrameSize,希望将其居中于中心。然后应用转换。尽管进行了多次尝试,我还是得到了奇怪的位置和奇怪的剪裁。
所以我只是在寻找一个 simple/straightforward 方法,它可以将 CATextLayer
定位在视图中心给定的 radius/angle 处。
最后,我编写了一个带有滑块的单独应用程序:
- 帧原点
- 帧大小
- 边界原点
- 边界大小
- 位置
- 锚点
- 预旋转变换平移
- 变换旋转
- post旋转变换平移
以及一些显示图层 preferredFrameSize()
的调试信息。有了它,能够操纵事物并看到效果,我实际上能够非常简单地把它弄下来。
为给定单词(输入为 [CATextLayer]
)中的每个字符创建单个 CATextLayer
后,我使用以下方法定位单词的字符:
// pass 1 to:
// - baseline each characters rotation
// - size it and center it
// - accumulate the length of the whole word
var wordWidth:CGFloat = 0.0
for letter in word {
// baseline the transform first, because preferredFrameSize() is affected by any latent rotation in the current transform
letter.setAffineTransform(CGAffineTransformIdentity)
// the preferredFrameSize will now be the un rotated size of the single character
let size = letter.preferredFrameSize()
// move the letter to the center of the view
letter.frame = CGRect(origin: center - size.half, size: size)
// accumulate the length of the word as the length of characters
wordWidth += size.width
}
let Tau = CGFloat(M_PI * 2)
// the length of the circle we want to be relative to
let circumference = radius * Tau
// back up the "advance" to half of the word width
var advance = wordWidth.half.negated
for letter in word {
// retrieve the character dimensions
let charSize = letter.bounds.size
// advance half of the character, so we're at its center
advance += charSize.width.half
// determine the angle to rotate off of the nominal (which is 270 degrees)
let charRotation = (advance / circumference * Tau - (Tau * 3 / 4)) + angle
// start with an identity transform now
var transform = CGAffineTransformIdentity
// rotate it. this will rotate about the center of the character
transform = transform.rotated(charRotation)
// translate outwards to the ring
transform = transform.translated(CGPoint(x: 0, y: radius.negated))
// apply the transform
letter.setAffineTransform(transform)
// move the advance the other half of this character
advance += charSize.width.half
}
我正在尝试构建一个自定义控件,它可能看起来像这个 Inkscape 模型:
我在用 drawRect()
做事,但我需要为一些元素设置动画,所以我决定切换到 CALayer
的合成。我一直在使用 CGContextRef
函数绘制文本,但是为每个字符切换到 CATextLayer
后,我似乎无法正确转换它们。
我的一般方法是为每个角色创建一个 CATextLayer
。我认为我可以使用每一层的preferredFrameSize()
来获得角色的最终大小。然后我想我会在我的控件的 layoutSubviews()
方法中调整它们的位置和旋转。
"What have I tried?" 感觉自己一直坐在sit-n-spin上,蒙着眼睛,扔飞镖。你说出来,我试过了。
我可以通过以下方式确定每个角色的旋转角度:
let baseline = (self.ringBox.width * 3 / 8) // radius out to the baseline arc
let circumference = baseline * Tau
var advance = wordWidth.half.negated // wordWidth was summed from the width of each character earlier
let angle = 0.75.rotations
for each in self.autoCharacters {
let charSize = each.bounds.size
advance += charSize.width.half
let charRotation = (advance / circumference - 0.75).rotations + angle
...
}
我注意到 layoutSubviews()
似乎被调用了两次。为什么?我注意到第二次通过时,preferredFrameSize()
似乎变细了。为什么?是不是因为我设置了 affineTransform
并且它具有累积效应。我试图最初将层的位置设置为父框的中心,以及边界为 preferredFrameSize,希望将其居中于中心。然后应用转换。尽管进行了多次尝试,我还是得到了奇怪的位置和奇怪的剪裁。
所以我只是在寻找一个 simple/straightforward 方法,它可以将 CATextLayer
定位在视图中心给定的 radius/angle 处。
最后,我编写了一个带有滑块的单独应用程序:
- 帧原点
- 帧大小
- 边界原点
- 边界大小
- 位置
- 锚点
- 预旋转变换平移
- 变换旋转
- post旋转变换平移
以及一些显示图层 preferredFrameSize()
的调试信息。有了它,能够操纵事物并看到效果,我实际上能够非常简单地把它弄下来。
为给定单词(输入为 [CATextLayer]
)中的每个字符创建单个 CATextLayer
后,我使用以下方法定位单词的字符:
// pass 1 to:
// - baseline each characters rotation
// - size it and center it
// - accumulate the length of the whole word
var wordWidth:CGFloat = 0.0
for letter in word {
// baseline the transform first, because preferredFrameSize() is affected by any latent rotation in the current transform
letter.setAffineTransform(CGAffineTransformIdentity)
// the preferredFrameSize will now be the un rotated size of the single character
let size = letter.preferredFrameSize()
// move the letter to the center of the view
letter.frame = CGRect(origin: center - size.half, size: size)
// accumulate the length of the word as the length of characters
wordWidth += size.width
}
let Tau = CGFloat(M_PI * 2)
// the length of the circle we want to be relative to
let circumference = radius * Tau
// back up the "advance" to half of the word width
var advance = wordWidth.half.negated
for letter in word {
// retrieve the character dimensions
let charSize = letter.bounds.size
// advance half of the character, so we're at its center
advance += charSize.width.half
// determine the angle to rotate off of the nominal (which is 270 degrees)
let charRotation = (advance / circumference * Tau - (Tau * 3 / 4)) + angle
// start with an identity transform now
var transform = CGAffineTransformIdentity
// rotate it. this will rotate about the center of the character
transform = transform.rotated(charRotation)
// translate outwards to the ring
transform = transform.translated(CGPoint(x: 0, y: radius.negated))
// apply the transform
letter.setAffineTransform(transform)
// move the advance the other half of this character
advance += charSize.width.half
}