Add analog clock story stickers

This commit is contained in:
Elaine 2023-08-30 14:42:53 -06:00 committed by GitHub
parent ce6e94a3a5
commit ae59ef4b91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 494 additions and 2 deletions

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-arabic-hour.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-arabic-minute.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-arabic.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-baton-hour.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-baton-minute.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-baton.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-diver-center.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-diver-hour.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-diver-minute.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-diver.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-explorer-hour.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-explorer-minute.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "clock-explorer.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -6,6 +6,24 @@
import UIKit
import SignalMessaging
private class LayerContainerView: UIView {
let contentLayer: CALayer
init(contentLayer: CALayer) {
self.contentLayer = contentLayer
super.init(frame: .zero)
layer.addSublayer(contentLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
contentLayer.frame = CGRect(origin: self.frame.origin, size: self.frame.size)
}
}
// MARK: - EditorSticker
public enum EditorSticker {
@ -16,6 +34,7 @@ public enum EditorSticker {
public enum StorySticker {
case clockDigital(DigitalClockStyle)
case clockAnalog(AnalogClockStyle)
func previewView() -> UIView {
switch self {
@ -24,6 +43,9 @@ public enum EditorSticker {
label.attributedText = digitalClockStyle.attributedString(date: Date())
label.adjustsFontSizeToFitWidth = true
return label
case .clockAnalog(let clockStyle):
let clockLayer = clockStyle.drawClock(date: Date())
return LayerContainerView(contentLayer: clockLayer)
}
}
@ -33,6 +55,7 @@ public enum EditorSticker {
static var pickerStickers: [StorySticker] {
[
.clockDigital(.white),
.clockAnalog(.arabic),
]
}
}
@ -123,3 +146,236 @@ extension EditorSticker.StorySticker {
}
}
}
// MARK: AnalogClockStyle
extension EditorSticker.StorySticker {
public enum AnalogClockStyle: CaseIterable {
case arabic
case baton
case explorer
case diver
var backgroundImage: UIImage {
switch self {
case .arabic:
return #imageLiteral(resourceName: "clock-arabic.pdf")
case .baton:
return #imageLiteral(resourceName: "clock-baton.pdf")
case .explorer:
return #imageLiteral(resourceName: "clock-explorer.pdf")
case .diver:
return #imageLiteral(resourceName: "clock-diver.pdf")
}
}
func drawClock(date: Date) -> CALayer {
return AnalogClockLayer(style: self, date: date)
}
var hourHandImage: UIImage {
switch self {
case .arabic:
return #imageLiteral(resourceName: "clock-arabic-hour.pdf")
case .baton:
return #imageLiteral(resourceName: "clock-baton-hour.pdf")
case .explorer:
return #imageLiteral(resourceName: "clock-explorer-hour.pdf")
case .diver:
return #imageLiteral(resourceName: "clock-diver-hour.pdf")
}
}
var hourHandHeight: CGFloat {
switch self {
case .arabic:
return 1/3
case .baton:
return 0.35
case .explorer:
return 149/600
case .diver:
return 139/600
}
}
var hourHandOffset: CGFloat {
switch self {
case .arabic:
return 0.72
case .baton:
return 16/21
case .explorer:
return 1
case .diver:
return 141/139
}
}
var minuteHandImage: UIImage {
switch self {
case .arabic:
return #imageLiteral(resourceName: "clock-arabic-minute.pdf")
case .baton:
return #imageLiteral(resourceName: "clock-baton-minute.pdf")
case .explorer:
return #imageLiteral(resourceName: "clock-explorer-minute.pdf")
case .diver:
return #imageLiteral(resourceName: "clock-diver-minute.pdf")
}
}
var minuteHandHeight: CGFloat {
switch self {
case .arabic:
return 280/600
case .baton:
return 308/600
case .explorer:
return 229/600
case .diver:
return 268/600
}
}
var minuteHandOffset: CGFloat {
switch self {
case .arabic:
return 4/5
case .baton:
return 129/154
case .explorer:
return 1
case .diver:
return 1
}
}
var centerImage: UIImage? {
switch self {
case .diver:
return #imageLiteral(resourceName: "clock-diver-center.pdf")
case .arabic, .baton, .explorer:
return nil
}
}
func nextStyle() -> AnalogClockStyle {
switch self {
case .arabic:
return .baton
case .baton:
return .explorer
case .explorer:
return .diver
case .diver:
return .arabic
}
}
func stickerWithNextStyle() -> EditorSticker {
return .story(.clockAnalog(self.nextStyle()))
}
}
}
// MARK: - AnalogClockLayer
private class AnalogClockLayer: CALayer {
typealias Style = EditorSticker.StorySticker.AnalogClockStyle
private let clockStyle: Style
private let date: Date
private let background: CALayer
private let hourHand: CALayer
private let minuteHand: CALayer
private let center: CALayer?
override var frame: CGRect {
didSet {
updateSublayerFrames()
}
}
init(style: Style, date: Date) {
self.clockStyle = style
self.date = date
background = UIImageView(image: style.backgroundImage).layer
let hourHandImageView = UIImageView(image: style.hourHandImage)
hourHandImageView.contentMode = .scaleAspectFit
hourHand = hourHandImageView.layer
let minuteHandImageView = UIImageView(image: style.minuteHandImage)
minuteHandImageView.contentMode = .scaleAspectFit
minuteHand = minuteHandImageView.layer
center = style.centerImage.map(UIImageView.init(image:))?.layer
super.init()
addSublayer(background)
addSublayer(hourHand)
addSublayer(minuteHand)
if let center {
addSublayer(center)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateSublayerFrames() {
let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: date)
let minutes = CGFloat(dateComponents.minute ?? 0)
let hours = CGFloat(dateComponents.hour ?? 0) + minutes/60
// let minutes = CGFloat.random(in: 0..<60)
// let hours = CGFloat.random(in: 0..<12)
background.frame.size = self.frame.size
transfrom(
clockHandLayer: hourHand,
time: hours/12,
height: clockStyle.hourHandHeight,
offset: clockStyle.hourHandOffset
)
transfrom(
clockHandLayer: minuteHand,
time: minutes/60,
height: clockStyle.minuteHandHeight,
offset: clockStyle.minuteHandOffset
)
if let center {
let size: CGFloat = 42/600 * self.frame.height
center.frame = CGRect(
origin: .init(
x: self.frame.width/2 - size/2,
y: self.frame.height/2 - size/2
),
size: .square(size)
)
}
}
private func transfrom(
clockHandLayer hand: CALayer,
time: CGFloat,
height: CGFloat,
offset: CGFloat
) {
hand.setAffineTransform(.identity)
hand.frame.size.height = self.frame.height * height
hand.frame.origin = .init(
x: self.frame.width/2 - hand.frame.size.width/2,
y: self.frame.height/2 - hand.frame.size.height/2
)
hand.anchorPoint = .init(x: 0.5, y: offset)
hand.setAffineTransform(
.init(translationX: 0, y: -hand.frame.height * (offset - 0.5))
.rotated(by: time * 2 * .pi)
)
}
}

View File

@ -988,7 +988,7 @@ class ImageEditorCanvasView: UIView {
model.stickerViewCache.setObject(ThreadSafeCacheHandle(stickerImage), forKey: item.itemId)
image = stickerImage
case (.none, .story(let storySticker)):
// Special sticker. Return early with special rendering
// Story sticker. Return early with special rendering
return storyStickerLayer(
for: storySticker,
imageFrame: imageFrame,
@ -1002,8 +1002,27 @@ class ImageEditorCanvasView: UIView {
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFit
let imageLayer = imageView.layer
return makeLayerTransformable(
imageView.layer,
imageFrame: imageFrame,
item: item,
model: model,
isFaded: isFaded,
transform: transform,
viewSize: viewSize
)
}
private class func makeLayerTransformable(
_ imageLayer: CALayer,
imageFrame: CGRect,
item: ImageEditorStickerItem,
model: ImageEditorModel,
isFaded: Bool,
transform: ImageEditorTransform,
viewSize: CGSize
) -> CALayer {
imageLayer.contentsScale = UIScreen.main.scale * item.scaling * transform.scaling
let stickerSize = CGSize(square: 175 * imageFrame.size.width / item.referenceImageWidth)
let centerInCanvas = item.unitCenter.fromUnitCoordinates(viewBounds: imageFrame)
@ -1059,6 +1078,18 @@ class ImageEditorCanvasView: UIView {
transform: transform,
viewSize: viewSize
)
case .clockAnalog(let clockStyle):
let clockLayer = clockStyle.drawClock(date: item.date)
return makeLayerTransformable(
clockLayer,
imageFrame: imageFrame,
item: item,
model: model,
isFaded: isFaded,
transform: transform,
viewSize: viewSize
)
}
}

View File

@ -321,6 +321,10 @@ class ImageEditorView: UIView {
let newSticker = clockStyle.stickerWithNextStyle()
let newStickerItem = stickerItem.copy(sticker: newSticker)
model.replace(item: newStickerItem)
case .clockAnalog(let clockStyle):
let newSticker = clockStyle.stickerWithNextStyle()
let newStickerItem = stickerItem.copy(sticker: newSticker)
model.replace(item: newStickerItem)
}
}
}