From 774a1ab1c783baaec7e38a25a86981a4a1214a81 Mon Sep 17 00:00:00 2001 From: bullinnyc Date: Wed, 12 Jun 2024 12:16:55 +0700 Subject: [PATCH 1/5] Minor improvements. --- Examples/ExampleArtLetterView.swift | 12 +- Examples/ExampleBubbleView.swift | 12 +- Examples/ExampleCharmedLetterView.swift | 19 +- Examples/ExampleCharmedView.swift | 13 +- Examples/ExampleFantasyView.swift | 12 +- README.md | 68 +-- .../AnimationCompletionViewModifier.swift | 3 +- .../Extensions/Extension+TextAlignment.swift | 24 + .../Colors/yinYang.colorset/Contents.json | 38 ++ .../Resources/ResourcesManager.swift | 7 + Sources/MagicText/Services/DataManager.swift | 4 +- Sources/MagicText/Views/ArtLetterView.swift | 10 +- Sources/MagicText/Views/BubbleView.swift | 10 +- .../MagicText/Views/CharmedLetterView.swift | 8 +- Sources/MagicText/Views/CharmedView.swift | 4 +- Sources/MagicText/Views/FantasyView.swift | 10 +- Sources/MagicText/Views/MagicTextView.swift | 441 ++++++++++++++++++ Sources/MagicText/Views/MagicView.swift | 310 ------------ 18 files changed, 583 insertions(+), 422 deletions(-) create mode 100644 Sources/MagicText/Extensions/Extension+TextAlignment.swift create mode 100644 Sources/MagicText/Resources/Assets.xcassets/Colors/yinYang.colorset/Contents.json create mode 100644 Sources/MagicText/Views/MagicTextView.swift delete mode 100644 Sources/MagicText/Views/MagicView.swift diff --git a/Examples/ExampleArtLetterView.swift b/Examples/ExampleArtLetterView.swift index 0dae1d0..b3ca228 100644 --- a/Examples/ExampleArtLetterView.swift +++ b/Examples/ExampleArtLetterView.swift @@ -27,30 +27,30 @@ struct ExampleArtLetterView: View { RM.night, RM.sky, RM.smoke, RM.theia, RM.venus ] - // MARK: - Body Property + // MARK: - Body var body: some View { VStack(spacing: 25) { // Art letter magic text. - MagicView( + MagicTextView( text: singleLineExampleText, - textColors: [.black.opacity(0.7)], + textColors: [Color(RM.yinYang).opacity(0.8)], fontSize: 28, magicType: .artLetter ) { - print("Animation finished") + print("Animation finished.") } .padding(.bottom) // Art letter magic text with different colors. - MagicView( + MagicTextView( text: multiLineExampleText, textColors: colors.map { Color($0) }, fontSize: 28, magicType: .artLetter, delayStart: 5 ) { - print("Animation finished") + print("Animation finished.") } } } diff --git a/Examples/ExampleBubbleView.swift b/Examples/ExampleBubbleView.swift index 7fde6d1..deb12a7 100644 --- a/Examples/ExampleBubbleView.swift +++ b/Examples/ExampleBubbleView.swift @@ -27,30 +27,30 @@ struct ExampleBubbleView: View { RM.night, RM.sky, RM.smoke, RM.theia, RM.venus ] - // MARK: - Body Property + // MARK: - Body var body: some View { VStack(spacing: 25) { // Bubble magic text. - MagicView( + MagicTextView( text: singleLineExampleText, - textColors: [.black.opacity(0.7)], + textColors: [Color(RM.yinYang).opacity(0.8)], fontSize: 28, magicType: .bubble ) { - print("Animation finished") + print("Animation finished.") } .padding(.bottom) // Bubble magic text with different colors. - MagicView( + MagicTextView( text: multiLineExampleText, textColors: colors.map { Color($0) }, fontSize: 28, magicType: .bubble, delayStart: 5 ) { - print("Animation finished") + print("Animation finished.") } } } diff --git a/Examples/ExampleCharmedLetterView.swift b/Examples/ExampleCharmedLetterView.swift index 95a1127..b0a3ea7 100644 --- a/Examples/ExampleCharmedLetterView.swift +++ b/Examples/ExampleCharmedLetterView.swift @@ -22,35 +22,30 @@ struct ExampleCharmedLetterView: View { gonna get. """ - private let colors = [ - RM.candy, RM.coffee, RM.flower, RM.forest, RM.newyork, - RM.night, RM.sky, RM.smoke, RM.theia, RM.venus - ] - - // MARK: - Body Property + // MARK: - Body var body: some View { VStack(spacing: 25) { // Charmed letter magic text. - MagicView( + MagicTextView( text: singleLineExampleText, - textColors: [.black.opacity(0.7)], + textColors: [Color(RM.yinYang).opacity(0.8)], fontSize: 28, magicType: .charmedLetter() ) { - print("Animation finished") + print("Animation finished.") } .padding(.bottom) // Charmed letter magic text with different colors. - MagicView( + MagicTextView( text: multiLineExampleText, - textColors: colors.map { Color($0) }, + textColors: [Color(RM.yinYang).opacity(0.8)], fontSize: 28, magicType: .charmedLetter(backgroundColor: .blue), delayStart: 5 ) { - print("Animation finished") + print("Animation finished.") } } } diff --git a/Examples/ExampleCharmedView.swift b/Examples/ExampleCharmedView.swift index 804adab..c5c1757 100644 --- a/Examples/ExampleCharmedView.swift +++ b/Examples/ExampleCharmedView.swift @@ -27,33 +27,34 @@ struct ExampleCharmedView: View { RM.night, RM.sky, RM.smoke, RM.theia, RM.venus ] - // MARK: - Body Property + // MARK: - Body var body: some View { VStack(spacing: 25) { // Charmed magic text (default). - MagicView( + MagicTextView( text: singleLineExampleText, fontSize: 28 ) { - print("Animation finished") + print("Animation finished.") } .padding(.bottom) // Charmed magic text with different colors. - MagicView( + MagicTextView( text: multiLineExampleText, - textColors: [.black, .red, .orange, .yellow, .green, .blue], + textColors: colors.map { Color($0) }, fontSize: 28, delayStart: 5 ) { - print("Animation finished") + print("Animation finished.") } } } } // MARK: - Preview Provider + struct ExampleCharmedView_Previews: PreviewProvider { static var previews: some View { ExampleCharmedView() diff --git a/Examples/ExampleFantasyView.swift b/Examples/ExampleFantasyView.swift index c424ec8..663689a 100644 --- a/Examples/ExampleFantasyView.swift +++ b/Examples/ExampleFantasyView.swift @@ -27,30 +27,30 @@ struct ExampleFantasyView: View { RM.night, RM.sky, RM.smoke, RM.theia, RM.venus ] - // MARK: - Body Property + // MARK: - Body var body: some View { VStack(spacing: 25) { // Fantasy magic text. - MagicView( + MagicTextView( text: singleLineExampleText, - textColors: [.black.opacity(0.7)], + textColors: [Color(RM.yinYang).opacity(0.8)], fontSize: 28, magicType: .fantasy ) { - print("Animation finished") + print("Animation finished.") } .padding(.bottom) // Fantasy magic text with different colors. - MagicView( + MagicTextView( text: multiLineExampleText, textColors: colors.map { Color($0) }, fontSize: 28, magicType: .fantasy, delayStart: 5 ) { - print("Animation finished") + print("Animation finished.") } } } diff --git a/README.md b/README.md index 2962901..fd1eea8 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,18 @@ [![Swift Package Manager compatible](https://img.shields.io/badge/SPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) -Magic text is the perfect solution when you need to show a small amount of information in a pretty wrapper. +MagicText is the perfect solution when you need to show a small amount of information in a pretty wrapper. ## Gifs + ![](./charmed.gif) ![](./charmed-color.gif) ![](./bubble.gif) ![](./fantasy.gif) ![](./charmed-letter.gif) ![](./art-letter.gif) ## Installation + ### [Swift Package Manager](https://swift.org/package-manager/) -Going to Xcode `File` > `Add Packages...` and add the repository by giving the URL `https://github.com/bullinnyc/MagicText` +Going to Xcode `File` > `Add Packages...` and add the repository by giving the URL +`https://github.com/bullinnyc/MagicText` Enjoy! ## Usage @@ -30,78 +33,41 @@ gonna get. """ // Charmed magic text (default). -MagicView( +MagicTextView( text: singleLineExampleText, fontSize: 28 ) { - print("Animation finished") -} - -// Charmed magic text with different colors. -MagicView( - text: multiLineExampleText, - textColors: [.red, .orange, .yellow, .green, .blue, .black], - fontSize: 28, - delayStart: 5 -) { - print("Animation finished") -} - -// Art letter magic text. -MagicView( - text: singleLineExampleText, - textColors: [.black.opacity(0.7)], - fontSize: 28, - magicType: .artLetter -) { - print("Animation finished") -} - -// Bubble magic text. -MagicView( - text: singleLineExampleText, - textColors: [.black.opacity(0.7)], - fontSize: 28, - magicType: .bubble -) { - print("Animation finished") + print("Animation finished.") } // Fantasy magic text. -MagicView( - text: singleLineExampleText, - textColors: [.black.opacity(0.7)], +MagicTextView( + text: multiLineExampleText, + textColors: [.black.opacity(0.8)], fontSize: 28, magicType: .fantasy ) { - print("Animation finished") -} - -// Charmed letter magic text. -MagicView( - text: singleLineExampleText, - textColors: [.black.opacity(0.7)], - fontSize: 28, - magicType: .charmedLetter(backgroundColor: .blue) -) { - print("Animation finished") + print("Animation finished.") } ``` -### Sets the type of `MagicText` -**Note:** Default type is set to `charmed`. You can try other types. +### Set the type of MagicText +**Note:** Default `magicType` is set to `charmed`. +You can try other types. ```swift .charmed .artLetter .bubble .fantasy -.charmedLetter +.charmedLetter(backgroundColor:) ``` ## Requirements + - iOS 14.0 + - [SwiftUI](https://developer.apple.com/xcode/swiftui/) ## License + - MagicText is distributed under the MIT License. diff --git a/Sources/MagicText/AnimatableModifiers/AnimationCompletionViewModifier.swift b/Sources/MagicText/AnimatableModifiers/AnimationCompletionViewModifier.swift index 95d93f2..5813d6d 100644 --- a/Sources/MagicText/AnimatableModifiers/AnimationCompletionViewModifier.swift +++ b/Sources/MagicText/AnimatableModifiers/AnimationCompletionViewModifier.swift @@ -9,8 +9,7 @@ import SwiftUI struct AnimationCompletionAnimatableModifier: - AnimatableModifier where Value: VectorArithmetic -{ + AnimatableModifier where Value: VectorArithmetic { // MARK: - Animatable Protocol Properties var animatableData: Value { diff --git a/Sources/MagicText/Extensions/Extension+TextAlignment.swift b/Sources/MagicText/Extensions/Extension+TextAlignment.swift new file mode 100644 index 0000000..42f0d19 --- /dev/null +++ b/Sources/MagicText/Extensions/Extension+TextAlignment.swift @@ -0,0 +1,24 @@ +// +// Extension+TextAlignment.swift +// MagicText +// +// Created by Dmitry Kononchuk on 10.11.2023. +// Copyright © 2023 Dmitry Kononchuk. All rights reserved. +// + +import SwiftUI + +extension TextAlignment { + // MARK: - Public Properties + + var toHorizontalAlignment: HorizontalAlignment { + switch self { + case .leading: + .leading + case .center: + .center + case .trailing: + .trailing + } + } +} diff --git a/Sources/MagicText/Resources/Assets.xcassets/Colors/yinYang.colorset/Contents.json b/Sources/MagicText/Resources/Assets.xcassets/Colors/yinYang.colorset/Contents.json new file mode 100644 index 0000000..3fe9b59 --- /dev/null +++ b/Sources/MagicText/Resources/Assets.xcassets/Colors/yinYang.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/MagicText/Resources/ResourcesManager.swift b/Sources/MagicText/Resources/ResourcesManager.swift index 3dbc1eb..9222be5 100644 --- a/Sources/MagicText/Resources/ResourcesManager.swift +++ b/Sources/MagicText/Resources/ResourcesManager.swift @@ -16,6 +16,13 @@ public typealias RM = ResourcesManager public final class ResourcesManager { // MARK: - Public Properties + /// An object that stores color data. + public static let yinYang = UIColor( + named: "yinYang", + in: Bundle.module, + compatibleWith: nil + ) ?? UIColor() + /// An object that stores color data. public static let candy = UIColor( named: "candy", diff --git a/Sources/MagicText/Services/DataManager.swift b/Sources/MagicText/Services/DataManager.swift index 0ad7fd9..1b2820b 100644 --- a/Sources/MagicText/Services/DataManager.swift +++ b/Sources/MagicText/Services/DataManager.swift @@ -8,10 +8,10 @@ import Foundation -class DataManager { +final class DataManager { // MARK: - Public Properties - static let colorsName = [ + static let colors = [ RM.candy, RM.coffee, RM.flower, RM.forest, RM.newyork, RM.night, RM.sky, RM.smoke, RM.theia, RM.venus ] diff --git a/Sources/MagicText/Views/ArtLetterView.swift b/Sources/MagicText/Views/ArtLetterView.swift index afff275..45d1e59 100644 --- a/Sources/MagicText/Views/ArtLetterView.swift +++ b/Sources/MagicText/Views/ArtLetterView.swift @@ -23,15 +23,15 @@ struct ArtLetterView: View { let delay: Double let completion: () -> Void - // MARK: - Body Property + // MARK: - Body var body: some View { let updatedSymbol = symbol == " " ? " " : symbol - let colors = DataManager.colorsName.map { Color($0) } + let colors = DataManager.colors.map { Color($0) } let color = opacity == 1 && symbol != " " - ? colors.randomElement() ?? .clear - : .clear + ? colors.randomElement() ?? .clear + : .clear Text(updatedSymbol) .font(.custom(fontName, size: fontSize)) @@ -59,7 +59,7 @@ struct ArtLetterView_Previews: PreviewProvider { static var previews: some View { ArtLetterView( symbol: "K", - textColor: .black, + textColor: Color(RM.yinYang), fontName: "Baskerville", fontSize: 28, delay: 0 diff --git a/Sources/MagicText/Views/BubbleView.swift b/Sources/MagicText/Views/BubbleView.swift index d71a40e..72faaf7 100644 --- a/Sources/MagicText/Views/BubbleView.swift +++ b/Sources/MagicText/Views/BubbleView.swift @@ -24,7 +24,7 @@ struct BubbleView: View { let delay: Double let completion: () -> Void - // MARK: - Body Property + // MARK: - Body var body: some View { let updatedSymbol = symbol == " " ? " " : symbol @@ -32,11 +32,11 @@ struct BubbleView: View { Group { if isMask { let fontScale = fontSize * Double.random(in: 0.7 ... 0.9) - let colors = DataManager.colorsName.map { Color($0) } + let colors = DataManager.colors.map { Color($0) } let color = blur == .zero && symbol != " " - ? colors.randomElement() ?? .clear - : .clear + ? colors.randomElement() ?? .clear + : .clear Text(updatedSymbol) .font(.custom(fontName, size: fontSize)) @@ -81,7 +81,7 @@ struct BubbleView_Previews: PreviewProvider { static var previews: some View { BubbleView( symbol: "K", - textColor: .black, + textColor: Color(RM.yinYang), fontName: "Baskerville", fontSize: 28, isMask: true, diff --git a/Sources/MagicText/Views/CharmedLetterView.swift b/Sources/MagicText/Views/CharmedLetterView.swift index ac1a0c3..998fd3a 100644 --- a/Sources/MagicText/Views/CharmedLetterView.swift +++ b/Sources/MagicText/Views/CharmedLetterView.swift @@ -25,7 +25,7 @@ struct CharmedLetterView: View { let delay: Double let completion: () -> Void - // MARK: - Body Property + // MARK: - Body var body: some View { let updatedSymbol = symbol == " " ? " " : symbol @@ -35,8 +35,8 @@ struct CharmedLetterView: View { let fontScale = fontSize * Double.random(in: 0.7 ... 0.9) let color = symbol != " " - ? backgroundColor - : .clear + ? backgroundColor + : .clear Text(updatedSymbol) .font(.custom(fontName, size: fontSize)) @@ -82,7 +82,7 @@ struct CharmedLetterView_Previews: PreviewProvider { static var previews: some View { CharmedLetterView( symbol: "K", - textColor: .black, + textColor: Color(RM.yinYang), fontName: "Baskerville", fontSize: 28, backgroundColor: .red, diff --git a/Sources/MagicText/Views/CharmedView.swift b/Sources/MagicText/Views/CharmedView.swift index 87477eb..b158060 100644 --- a/Sources/MagicText/Views/CharmedView.swift +++ b/Sources/MagicText/Views/CharmedView.swift @@ -24,7 +24,7 @@ struct CharmedView: View { let delay: Double let completion: () -> Void - // MARK: - Body Property + // MARK: - Body var body: some View { Group { @@ -69,7 +69,7 @@ struct CharmedView_Previews: PreviewProvider { static var previews: some View { CharmedView( symbol: "K", - textColor: .black, + textColor: Color(RM.yinYang), fontName: "Baskerville", fontSize: 28, isMask: true, diff --git a/Sources/MagicText/Views/FantasyView.swift b/Sources/MagicText/Views/FantasyView.swift index 73d1749..933d219 100644 --- a/Sources/MagicText/Views/FantasyView.swift +++ b/Sources/MagicText/Views/FantasyView.swift @@ -24,7 +24,7 @@ struct FantasyView: View { let delay: Double let completion: () -> Void - // MARK: - Body Property + // MARK: - Body var body: some View { let updatedSymbol = symbol == " " ? " " : symbol @@ -32,11 +32,11 @@ struct FantasyView: View { Group { if isMask { let fontScale = fontSize * Double.random(in: 0.7 ... 0.9) - let colors = DataManager.colorsName.map { Color($0) } + let colors = DataManager.colors.map { Color($0) } let color = blur == .zero && symbol != " " - ? colors.randomElement() ?? .clear - : .clear + ? colors.randomElement() ?? .clear + : .clear Text(updatedSymbol) .font(.custom(fontName, size: fontSize)) @@ -82,7 +82,7 @@ struct FantasyView_Previews: PreviewProvider { static var previews: some View { FantasyView( symbol: "K", - textColor: .black, + textColor: Color(RM.yinYang), fontName: "Baskerville", fontSize: 28, isMask: true, diff --git a/Sources/MagicText/Views/MagicTextView.swift b/Sources/MagicText/Views/MagicTextView.swift new file mode 100644 index 0000000..65b1830 --- /dev/null +++ b/Sources/MagicText/Views/MagicTextView.swift @@ -0,0 +1,441 @@ +// +// MagicTextView.swift +// MagicText +// +// Created by Dmitry Kononchuk on 20.04.2023. +// Copyright © 2023 Dmitry Kononchuk. All rights reserved. +// + +import SwiftUI + +/// Magic text view. +public struct MagicTextView: View { + // MARK: - Public Enums + + public enum MagicType { + case charmed + case artLetter + case bubble + case fantasy + case charmedLetter(backgroundColor: Color = Color(RM.candy)) + } + + // MARK: - Property Wrappers + + @State private var animationCounter = 0 + + // MARK: - Private Properties + + private let text: String + private let textColors: [Color] + private let textAlignment: TextAlignment + private let fontName: String + private let fontSize: CGFloat + private let minCharactersPerLine: Int + private let magicType: MagicType + private let delayStart: Double + private let completion: (() -> Void)? + + // MARK: - Initializers + + /// - Parameters: + /// - text: Text to be displayed. + /// - textColors: Colors for text. + /// - textAlignment: Text alignment. + /// - fontName: Font name. + /// - fontSize: Font size. + /// - minCharactersPerLine: Minimum number of characters per line. + /// This option is only enabled if the text is a single line. + /// If the text is multiline, then this option is disabled, + /// you have to take care of text wrapping in multiline text. + /// - magicType: View type for text. + /// - delayStart: Delay for text visibility. + /// - completion: A block object to be executed when the animation sequence ends. + public init( + text: String, + textColors: [Color] = [Color(RM.yinYang)], + textAlignment: TextAlignment = .center, + fontName: String = "Baskerville", + fontSize: CGFloat, + minCharactersPerLine: Int = 20, + magicType: MagicType = .charmed, + delayStart: Double = 0, + completion: (() -> Void)? = nil + ) { + self.text = text + self.textColors = textColors + self.textAlignment = textAlignment + self.fontName = fontName + self.fontSize = fontSize + self.minCharactersPerLine = minCharactersPerLine + self.magicType = magicType + self.delayStart = delayStart + self.completion = completion + } + + // MARK: - Body + + public var body: some View { + VStack(alignment: textAlignment.toHorizontalAlignment) { + let linesOfText = createLinesOfText( + text: text, + minCharactersPerLine: minCharactersPerLine + ) + + let textCount = linesOfText.joined().count + + ForEach(linesOfText, id: \.self) { element in + HStack(spacing: 0.5) { + ForEach(0 ..< element.count, id: \.self) { index in + let perLineDelay = delayStart + + Double.random(in: 0.1 ... 1.8) + + let firstDelay = perLineDelay + Double(index) * 0.5 * + Double.random(in: 0.004 ... 0.5) + + let secondDelay = perLineDelay + Double(index) * + Double.random(in: 0.1 ... 0.3) + + let symbol = String(Array(element)[index]) + + let textColor = textColors.count == 1 + ? textColors.first ?? .black + : textColors.randomElement() ?? .black + + switch magicType { + case .charmed: + charmed( + symbol: symbol, + textColor: textColor, + textCount: textCount, + firstDelay: firstDelay, + secondDelay: secondDelay + ) + case .artLetter: + artLetter( + symbol: symbol, + textColor: textColor, + textCount: textCount, + firstDelay: firstDelay + ) + case .bubble: + bubble( + symbol: symbol, + textColor: textColor, + textCount: textCount, + firstDelay: firstDelay, + secondDelay: secondDelay + ) + case .fantasy: + fantasy( + symbol: symbol, + textColor: textColor, + textCount: textCount, + firstDelay: firstDelay, + secondDelay: secondDelay + ) + case .charmedLetter(let backgroundColor): + charmedLetter( + symbol: symbol, + textColor: textColor, + textCount: textCount, + backgroundColor: backgroundColor, + firstDelay: firstDelay, + secondDelay: secondDelay + ) + } + } + } + } + } + .fixedSize() + } + + // MARK: - Private Methods + + private func createLinesOfText( + text: String, + minCharactersPerLine: Int + ) -> [String] { + var linesOfText: [String] = [] + var lineOfText = "" + + let isNewline = text.first(where: { $0.isNewline }) != nil + + text.enumerated().forEach { index, character in + if isNewline { + if character == "\n" { + linesOfText.append(lineOfText) + lineOfText = "" + } else { + lineOfText.append(character) + + if index == text.count - 1 { + linesOfText.append(lineOfText) + } + } + } else { + if lineOfText.count >= minCharactersPerLine, character == " " { + linesOfText.append(lineOfText) + lineOfText = "" + } else { + lineOfText.append(character) + + if index == text.count - 1 { + linesOfText.append(lineOfText) + } + } + } + } + + return linesOfText + } + + private func checkLastAnimation(_ textCount: Int) { + animationCounter += 1 + + let animationCount: Int + + switch magicType { + case .artLetter: + animationCount = textCount + default: + animationCount = textCount * 2 + } + + if animationCounter == animationCount { + completion?() + } + } +} + +// MARK: - Ext. Configure views + +extension MagicTextView { + private func charmed( + symbol: String, + textColor: Color, + textCount: Int, + firstDelay: Double, + secondDelay: Double + ) -> some View { + ZStack { + CharmedView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + isMask: true, + delay: firstDelay + ) { + checkLastAnimation(textCount) + } + + CharmedView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + isMask: false, + delay: secondDelay + ) { + checkLastAnimation(textCount) + } + } + } + + @ViewBuilder + private func artLetter( + symbol: String, + textColor: Color, + textCount: Int, + firstDelay: Double + ) -> some View { + let scale = CGFloat.random(in: 0.5 ... 1.1) + let degrees = Double.random(in: -10 ... 10) + let offset = CGFloat.random(in: -4 ... 4) + + ArtLetterView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + delay: firstDelay + ) { + checkLastAnimation(textCount) + } + .scaleEffect(scale) + .rotationEffect(.degrees(degrees)) + .offset(y: offset) + } + + private func bubble( + symbol: String, + textColor: Color, + textCount: Int, + firstDelay: Double, + secondDelay: Double + ) -> some View { + ZStack { + let scale = Double.random(in: 0.5 ... 1.1) + let offset = CGFloat.random(in: -2 ... 2) + + Group { + BubbleView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + isMask: true, + delay: firstDelay + ) { + checkLastAnimation(textCount) + } + + BubbleView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + isMask: false, + delay: secondDelay + ) { + checkLastAnimation(textCount) + } + } + .scaleEffect(scale) + .offset(y: offset) + } + } + + private func fantasy( + symbol: String, + textColor: Color, + textCount: Int, + firstDelay: Double, + secondDelay: Double + ) -> some View { + ZStack { + let scale = Double.random(in: 0.5 ... 1.1) + let offset = CGFloat.random(in: -2 ... 2) + + Group { + FantasyView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + isMask: true, + delay: firstDelay + ) { + checkLastAnimation(textCount) + } + + FantasyView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + isMask: false, + delay: secondDelay + ) { + checkLastAnimation(textCount) + } + } + .scaleEffect(scale) + .offset(y: offset) + } + } + + private func charmedLetter( + symbol: String, + textColor: Color, + textCount: Int, + backgroundColor: Color, + firstDelay: Double, + secondDelay: Double + ) -> some View { + ZStack { + let scale = Double.random(in: 0.5 ... 1.1) + let offset = CGFloat.random(in: -2 ... 2) + + Group { + CharmedLetterView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + backgroundColor: backgroundColor, + isMask: true, + delay: firstDelay + ) { + checkLastAnimation(textCount) + } + + CharmedLetterView( + symbol: symbol, + textColor: textColor, + fontName: fontName, + fontSize: fontSize, + backgroundColor: nil, + isMask: false, + delay: secondDelay + ) { + checkLastAnimation(textCount) + } + } + .scaleEffect(scale) + .offset(y: offset) + } + } +} + +// MARK: - Preview Provider + +struct MagicTextView_Previews: PreviewProvider { + static var previews: some View { + let text = "Life is like a box of chocolates, you never know what you’re gonna get." + + let fontSize: CGFloat = 28 + let minCharactersPerLine = 10 + + MagicTextView( + text: text, + fontSize: fontSize, + minCharactersPerLine: minCharactersPerLine, + magicType: .charmed + ) + + MagicTextView( + text: text, + textColors: [Color(RM.yinYang).opacity(0.8)], + fontSize: fontSize, + minCharactersPerLine: minCharactersPerLine, + magicType: .charmedLetter(backgroundColor: .blue) + ) + + MagicTextView( + text: text, + textColors: [Color(RM.yinYang).opacity(0.8)], + fontSize: fontSize, + minCharactersPerLine: minCharactersPerLine, + magicType: .bubble + ) + + MagicTextView( + text: text, + textColors: [Color(RM.yinYang).opacity(0.8)], + fontSize: fontSize, + minCharactersPerLine: minCharactersPerLine, + magicType: .fantasy + ) + + MagicTextView( + text: text, + textColors: [Color(RM.yinYang).opacity(0.8)], + fontSize: fontSize, + minCharactersPerLine: minCharactersPerLine, + magicType: .artLetter + ) + } +} diff --git a/Sources/MagicText/Views/MagicView.swift b/Sources/MagicText/Views/MagicView.swift deleted file mode 100644 index 5519184..0000000 --- a/Sources/MagicText/Views/MagicView.swift +++ /dev/null @@ -1,310 +0,0 @@ -// -// MagicView.swift -// MagicText -// -// Created by Dmitry Kononchuk on 20.04.2023. -// Copyright © 2023 Dmitry Kononchuk. All rights reserved. -// - -import SwiftUI - -/// Show a magic view. -public struct MagicView: View { - // MARK: - Property Wrappers - - @State private var animationCounter = 0 - - // MARK: - Private Properties - - private let text: String - private let textColors: [Color] - private let textAlignment: HorizontalAlignment - private let fontName: String - private let fontSize: CGFloat - private let minCharactersPerLine: Int - private let magicType: MagicType - private let delayStart: Double - private let completion: (() -> Void)? - - // MARK: - Public Enums - - public enum MagicType { - case charmed - case artLetter - case bubble - case fantasy - case charmedLetter(backgroundColor: Color = .red) - } - - // MARK: - Body Property - - public var body: some View { - VStack(alignment: textAlignment) { - let texts = createTexts( - text: text, - minCharactersPerLine: minCharactersPerLine - ) - - let textCount = texts.joined().count - - ForEach(texts, id: \.self) { element in - HStack(spacing: 0.5) { - ForEach(0 ..< element.count, id: \.self) { index in - let perLineDelay = delayStart + - Double.random(in: 0.1 ... 1.8) - - let firstDelay = perLineDelay + Double(index) * 0.5 * - Double.random(in: 0.004 ... 0.5) - - let secondDelay = perLineDelay + Double(index) * - Double.random(in: 0.1 ... 0.3) - - let symbol = String(Array(element)[index]) - let textColor = textColors.randomElement() ?? .black - - ZStack { - switch magicType { - case .charmed: - CharmedView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - isMask: true, - delay: firstDelay - ) { - checkLastAnimation(textCount) - } - - CharmedView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - isMask: false, - delay: secondDelay - ) { - checkLastAnimation(textCount) - } - case .artLetter: - let scale = CGFloat.random(in: 0.5 ... 1.1) - let degrees = Double.random(in: -10 ... 10) - let offset = CGFloat.random(in: -4 ... 4) - - ArtLetterView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - delay: firstDelay - ) { - checkLastAnimation(textCount) - } - .scaleEffect(scale) - .rotationEffect(.degrees(degrees)) - .offset(y: offset) - case .bubble: - let scale = Double.random(in: 0.5 ... 1.1) - let offset = CGFloat.random(in: -2 ... 2) - - Group { - BubbleView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - isMask: true, - delay: firstDelay - ) { - checkLastAnimation(textCount) - } - - BubbleView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - isMask: false, - delay: secondDelay - ) { - checkLastAnimation(textCount) - } - } - .scaleEffect(scale) - .offset(y: offset) - case .fantasy: - let scale = Double.random(in: 0.5 ... 1.1) - let offset = CGFloat.random(in: -2 ... 2) - - Group { - FantasyView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - isMask: true, - delay: firstDelay - ) { - checkLastAnimation(textCount) - } - - FantasyView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - isMask: false, - delay: secondDelay - ) { - checkLastAnimation(textCount) - } - } - .scaleEffect(scale) - .offset(y: offset) - case .charmedLetter(let backgroundColor): - let scale = Double.random(in: 0.5 ... 1.1) - let offset = CGFloat.random(in: -2 ... 2) - - Group { - CharmedLetterView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - backgroundColor: backgroundColor, - isMask: true, - delay: firstDelay - ) { - checkLastAnimation(textCount) - } - - CharmedLetterView( - symbol: symbol, - textColor: textColor, - fontName: fontName, - fontSize: fontSize, - backgroundColor: nil, - isMask: false, - delay: secondDelay - ) { - checkLastAnimation(textCount) - } - } - .scaleEffect(scale) - .offset(y: offset) - } - } - } - } - } - } - .fixedSize() - } - - // MARK: - Initializers - - /// - Parameters: - /// - text: Text to be displayed. - /// - textColors: Colors for text. Default value `.black`. - /// - textAlignment: Text alignment. - /// - fontName: Font name. - /// - fontSize: Font size. - /// - minCharactersPerLine: Minimum number of characters per line. - /// This option is only enabled if the text is a single line. - /// If the text is multiline, then this option is disabled, - /// you have to take care of text wrapping in multiline text. - /// - magicType: View type for text. - /// - delayStart: Delay for text visibility. - public init( - text: String, - textColors: [Color] = [.black], - textAlignment: HorizontalAlignment = .center, - fontName: String = "Baskerville", - fontSize: CGFloat, - minCharactersPerLine: Int = 20, - magicType: MagicType = .charmed, - delayStart: Double = 0, - completion: (() -> Void)? = nil - ) { - self.text = text - self.textColors = textColors - self.textAlignment = textAlignment - self.fontName = fontName - self.fontSize = fontSize - self.minCharactersPerLine = minCharactersPerLine - self.magicType = magicType - self.delayStart = delayStart - self.completion = completion - } - - // MARK: - Private Methods - - private func createTexts( - text: String, - minCharactersPerLine: Int - ) -> [String] { - var texts: [String] = [] - var lineOfText = "" - - let isNewline = text.first(where: { $0.isNewline }) != nil - - text.enumerated().forEach { index, character in - if isNewline { - if character == "\n" { - texts.append(lineOfText) - lineOfText = "" - } else { - lineOfText.append(character) - - if index == text.count - 1 { - texts.append(lineOfText) - } - } - } else { - if lineOfText.count >= minCharactersPerLine, character == " " { - texts.append(lineOfText) - lineOfText = "" - } else { - lineOfText.append(character) - - if index == text.count - 1 { - texts.append(lineOfText) - } - } - } - } - - return texts - } - - private func checkLastAnimation(_ textCount: Int) { - animationCounter += 1 - - let animationCount: Int - - switch magicType { - case .artLetter: - animationCount = textCount - default: - animationCount = textCount * 2 - } - - if animationCounter == animationCount { - completion?() - } - } -} - -// MARK: - Preview Provider - -struct MagicView_Previews: PreviewProvider { - static var previews: some View { - let text = "Life is like a box of chocolates, you never know what you’re gonna get." - - MagicView( - text: text, - fontSize: 28, - minCharactersPerLine: 10 - ) - } -} From ea33d71e88b261f8925cd3a5cc3ab1264da45ea3 Mon Sep 17 00:00:00 2001 From: bullinnyc Date: Wed, 12 Jun 2024 18:52:26 +0700 Subject: [PATCH 2/5] Add support macOS, tvOS, watchOS, visionOS. --- Examples/ExampleArtLetterView.swift | 2 - Examples/ExampleBubbleView.swift | 2 - Examples/ExampleCharmedLetterView.swift | 2 - Examples/ExampleCharmedView.swift | 2 - Examples/ExampleFantasyView.swift | 2 - Package.swift | 8 +- README.md | 3 +- .../Resources/ResourcesManager.swift | 198 ++++++++++++------ .../ViewModifiers/MaskViewModifier.swift | 9 +- 9 files changed, 151 insertions(+), 77 deletions(-) diff --git a/Examples/ExampleArtLetterView.swift b/Examples/ExampleArtLetterView.swift index b3ca228..f9aa045 100644 --- a/Examples/ExampleArtLetterView.swift +++ b/Examples/ExampleArtLetterView.swift @@ -6,7 +6,6 @@ // Copyright © 2023 Dmitry Kononchuk. All rights reserved. // -#if os(iOS) import SwiftUI import MagicText @@ -63,4 +62,3 @@ struct ExampleArtLetterView_Previews: PreviewProvider { ExampleArtLetterView() } } -#endif diff --git a/Examples/ExampleBubbleView.swift b/Examples/ExampleBubbleView.swift index deb12a7..6819ddf 100644 --- a/Examples/ExampleBubbleView.swift +++ b/Examples/ExampleBubbleView.swift @@ -6,7 +6,6 @@ // Copyright © 2023 Dmitry Kononchuk. All rights reserved. // -#if os(iOS) import SwiftUI import MagicText @@ -63,4 +62,3 @@ struct ExampleBubbleView_Previews: PreviewProvider { ExampleBubbleView() } } -#endif diff --git a/Examples/ExampleCharmedLetterView.swift b/Examples/ExampleCharmedLetterView.swift index b0a3ea7..1137369 100644 --- a/Examples/ExampleCharmedLetterView.swift +++ b/Examples/ExampleCharmedLetterView.swift @@ -6,7 +6,6 @@ // Copyright © 2023 Dmitry Kononchuk. All rights reserved. // -#if os(iOS) import SwiftUI import MagicText @@ -58,4 +57,3 @@ struct ExampleCharmedLetterView_Previews: PreviewProvider { ExampleCharmedLetterView() } } -#endif diff --git a/Examples/ExampleCharmedView.swift b/Examples/ExampleCharmedView.swift index c5c1757..62005b7 100644 --- a/Examples/ExampleCharmedView.swift +++ b/Examples/ExampleCharmedView.swift @@ -6,7 +6,6 @@ // Copyright © 2023 Dmitry Kononchuk. All rights reserved. // -#if os(iOS) import SwiftUI import MagicText @@ -60,4 +59,3 @@ struct ExampleCharmedView_Previews: PreviewProvider { ExampleCharmedView() } } -#endif diff --git a/Examples/ExampleFantasyView.swift b/Examples/ExampleFantasyView.swift index 663689a..4178170 100644 --- a/Examples/ExampleFantasyView.swift +++ b/Examples/ExampleFantasyView.swift @@ -6,7 +6,6 @@ // Copyright © 2023 Dmitry Kononchuk. All rights reserved. // -#if os(iOS) import SwiftUI import MagicText @@ -63,4 +62,3 @@ struct ExampleFantasyView_Previews: PreviewProvider { ExampleFantasyView() } } -#endif diff --git a/Package.swift b/Package.swift index 80525e0..6f51b49 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -7,7 +7,11 @@ let package = Package( name: "MagicText", platforms: [ // Add support for all platforms starting from a specific version. - .iOS(.v14) + .iOS(.v14), + .macOS(.v11), + .tvOS(.v14), + .watchOS(.v7), + .visionOS(.v1) ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. diff --git a/README.md b/README.md index fd1eea8..bd5acde 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ You can try other types. ## Requirements -- iOS 14.0 + +- iOS 14.0 + / macOS 11.0 + / tvOS 14.0 + / watchOS 7.0 + / visionOS 1.0 + +- Xcode 15.0 + - [SwiftUI](https://developer.apple.com/xcode/swiftui/) ## License diff --git a/Sources/MagicText/Resources/ResourcesManager.swift b/Sources/MagicText/Resources/ResourcesManager.swift index 9222be5..552081e 100644 --- a/Sources/MagicText/Resources/ResourcesManager.swift +++ b/Sources/MagicText/Resources/ResourcesManager.swift @@ -9,6 +9,10 @@ #if canImport(UIKit) import UIKit +#if os(watchOS) +import SwiftUI +#endif + /// Resources manager typealias. public typealias RM = ResourcesManager @@ -17,80 +21,148 @@ public final class ResourcesManager { // MARK: - Public Properties /// An object that stores color data. - public static let yinYang = UIColor( - named: "yinYang", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() + public static let yinYang = getColor(with: "yinYang") /// An object that stores color data. - public static let candy = UIColor( - named: "candy", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let candy = getColor(with: "candy") + /// An object that stores color data. - public static let coffee = UIColor( - named: "coffee", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let coffee = getColor(with: "coffee") + /// An object that stores color data. - public static let flower = UIColor( - named: "flower", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let flower = getColor(with: "flower") + /// An object that stores color data. - public static let forest = UIColor( - named: "forest", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let forest = getColor(with: "forest") + /// An object that stores color data. - public static let newyork = UIColor( - named: "newyork", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let newyork = getColor(with: "newyork") + /// An object that stores color data. - public static let night = UIColor( - named: "night", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let night = getColor(with: "night") + /// An object that stores color data. - public static let sky = UIColor( - named: "sky", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let sky = getColor(with: "sky") + /// An object that stores color data. - public static let smoke = UIColor( - named: "smoke", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() - + public static let smoke = getColor(with: "smoke") + /// An object that stores color data. - public static let theia = UIColor( - named: "theia", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() + public static let theia = getColor(with: "theia") + + /// An object that stores color data. + public static let venus = getColor(with: "venus") + + // MARK: - Public Methods + + /// Get image by name. + /// + /// - Parameter name: Image name. + /// + /// - Returns: An initialized image object or `nil` if the object was not found in the resources. + static func image(_ name: String) -> UIImage? { + UIImage(named: name, in: Bundle.module, with: nil) + } + + // MARK: - Private Methods + + private static func getColor(with name: String) -> UIColor { + #if os(watchOS) + UIColor(Color(name, bundle: Bundle.module)) + #elseif os(iOS) || os(tvOS) || os(visionOS) + UIColor( + named: name, + in: Bundle.module, + compatibleWith: nil + ) ?? UIColor() + #endif + } +} +#elseif canImport(AppKit) +import AppKit + +/// Resources manager typealias. +public typealias RM = ResourcesManager +/// Resources manager. +public final class ResourcesManager { + // MARK: - Public Properties + + /// An object that stores color data. + public static let yinYang = NSColor( + named: NSColor.Name("yinYang"), + bundle: Bundle.module + ) ?? NSColor() + /// An object that stores color data. - public static let venus = UIColor( - named: "venus", - in: Bundle.module, - compatibleWith: nil - ) ?? UIColor() + public static let candy = NSColor( + named: NSColor.Name("candy"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let coffee = NSColor( + named: NSColor.Name("coffee"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let flower = NSColor( + named: NSColor.Name("flower"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let forest = NSColor( + named: NSColor.Name("forest"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let newyork = NSColor( + named: NSColor.Name("newyork"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let night = NSColor( + named: NSColor.Name("night"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let sky = NSColor( + named: NSColor.Name("sky"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let smoke = NSColor( + named: NSColor.Name("smoke"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let theia = NSColor( + named: NSColor.Name("theia"), + bundle: Bundle.module + ) ?? NSColor() + + /// An object that stores color data. + public static let venus = NSColor( + named: NSColor.Name("venus"), + bundle: Bundle.module + ) ?? NSColor() + + // MARK: - Public Methods + + /// Get image by name. + /// + /// - Parameter name: Image name. + /// + /// - Returns: An initialized image object or `nil` if the object was not found in the resources. + static func image(_ name: String) -> NSImage? { + Bundle.module.image(forResource: NSImage.Name(name)) + } } #endif diff --git a/Sources/MagicText/ViewModifiers/MaskViewModifier.swift b/Sources/MagicText/ViewModifiers/MaskViewModifier.swift index 8b64171..d249d23 100644 --- a/Sources/MagicText/ViewModifiers/MaskViewModifier.swift +++ b/Sources/MagicText/ViewModifiers/MaskViewModifier.swift @@ -18,7 +18,14 @@ private struct MaskViewModifier: ViewModifier { // MARK: - Body Method func body(content: Content) -> some View { - if #available(iOS 15.0, *) { + if #available( + iOS 15.0, + macOS 12.0, + tvOS 15.0, + watchOS 8.0, + visionOS 1.0, + * + ) { content .mask { Text(text) From 479dd572202358bc9e28d9aede593d525ac99ab2 Mon Sep 17 00:00:00 2001 From: bullinnyc Date: Wed, 12 Jun 2024 19:27:55 +0700 Subject: [PATCH 3/5] Add extension UIColor. --- Examples/ExampleArtLetterView.swift | 4 +- Examples/ExampleBubbleView.swift | 4 +- Examples/ExampleCharmedLetterView.swift | 4 +- Examples/ExampleCharmedView.swift | 2 +- Examples/ExampleFantasyView.swift | 4 +- .../Extensions/Extension+UIColor.swift | 53 +++++++++++++++++++ Sources/MagicText/Views/ArtLetterView.swift | 4 +- Sources/MagicText/Views/BubbleView.swift | 4 +- .../MagicText/Views/CharmedLetterView.swift | 2 +- Sources/MagicText/Views/CharmedView.swift | 2 +- Sources/MagicText/Views/FantasyView.swift | 4 +- Sources/MagicText/Views/MagicTextView.swift | 12 ++--- 12 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 Sources/MagicText/Extensions/Extension+UIColor.swift diff --git a/Examples/ExampleArtLetterView.swift b/Examples/ExampleArtLetterView.swift index f9aa045..72900e2 100644 --- a/Examples/ExampleArtLetterView.swift +++ b/Examples/ExampleArtLetterView.swift @@ -33,7 +33,7 @@ struct ExampleArtLetterView: View { // Art letter magic text. MagicTextView( text: singleLineExampleText, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: 28, magicType: .artLetter ) { @@ -44,7 +44,7 @@ struct ExampleArtLetterView: View { // Art letter magic text with different colors. MagicTextView( text: multiLineExampleText, - textColors: colors.map { Color($0) }, + textColors: colors.map { $0.color }, fontSize: 28, magicType: .artLetter, delayStart: 5 diff --git a/Examples/ExampleBubbleView.swift b/Examples/ExampleBubbleView.swift index 6819ddf..d20bacd 100644 --- a/Examples/ExampleBubbleView.swift +++ b/Examples/ExampleBubbleView.swift @@ -33,7 +33,7 @@ struct ExampleBubbleView: View { // Bubble magic text. MagicTextView( text: singleLineExampleText, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: 28, magicType: .bubble ) { @@ -44,7 +44,7 @@ struct ExampleBubbleView: View { // Bubble magic text with different colors. MagicTextView( text: multiLineExampleText, - textColors: colors.map { Color($0) }, + textColors: colors.map { $0.color }, fontSize: 28, magicType: .bubble, delayStart: 5 diff --git a/Examples/ExampleCharmedLetterView.swift b/Examples/ExampleCharmedLetterView.swift index 1137369..a678b38 100644 --- a/Examples/ExampleCharmedLetterView.swift +++ b/Examples/ExampleCharmedLetterView.swift @@ -28,7 +28,7 @@ struct ExampleCharmedLetterView: View { // Charmed letter magic text. MagicTextView( text: singleLineExampleText, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: 28, magicType: .charmedLetter() ) { @@ -39,7 +39,7 @@ struct ExampleCharmedLetterView: View { // Charmed letter magic text with different colors. MagicTextView( text: multiLineExampleText, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: 28, magicType: .charmedLetter(backgroundColor: .blue), delayStart: 5 diff --git a/Examples/ExampleCharmedView.swift b/Examples/ExampleCharmedView.swift index 62005b7..baba79c 100644 --- a/Examples/ExampleCharmedView.swift +++ b/Examples/ExampleCharmedView.swift @@ -42,7 +42,7 @@ struct ExampleCharmedView: View { // Charmed magic text with different colors. MagicTextView( text: multiLineExampleText, - textColors: colors.map { Color($0) }, + textColors: colors.map { $0.color }, fontSize: 28, delayStart: 5 ) { diff --git a/Examples/ExampleFantasyView.swift b/Examples/ExampleFantasyView.swift index 4178170..9aeeeae 100644 --- a/Examples/ExampleFantasyView.swift +++ b/Examples/ExampleFantasyView.swift @@ -33,7 +33,7 @@ struct ExampleFantasyView: View { // Fantasy magic text. MagicTextView( text: singleLineExampleText, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: 28, magicType: .fantasy ) { @@ -44,7 +44,7 @@ struct ExampleFantasyView: View { // Fantasy magic text with different colors. MagicTextView( text: multiLineExampleText, - textColors: colors.map { Color($0) }, + textColors: colors.map { $0.color }, fontSize: 28, magicType: .fantasy, delayStart: 5 diff --git a/Sources/MagicText/Extensions/Extension+UIColor.swift b/Sources/MagicText/Extensions/Extension+UIColor.swift new file mode 100644 index 0000000..0a043ff --- /dev/null +++ b/Sources/MagicText/Extensions/Extension+UIColor.swift @@ -0,0 +1,53 @@ +// +// Extension+UIColor.swift +// MagicText +// +// Created by Dmitry Kononchuk on 12.06.2024. +// Copyright © 2024 Dmitry Kononchuk. All rights reserved. +// + +import SwiftUI + +#if canImport(UIKit) +extension UIColor { + // MARK: - Public Properties + + /// Creates a color from a UIKit color. + public var color: Color { + if #available( + iOS 15.0, + macOS 12.0, + tvOS 15.0, + watchOS 8.0, + visionOS 1.0, + * + ) { + Color(uiColor: self) + } else { + Color(self) + } + } +} +#elseif canImport(AppKit) +import AppKit + +extension NSColor { + // MARK: - Public Properties + + /// Creates a color from an AppKit color. + public var color: Color { + if #available( + iOS 15.0, + macOS 12.0, + tvOS 15.0, + watchOS 8.0, + visionOS 1.0, + * + ) { + Color(nsColor: self) + } else { + Color(self) + } + } +} +#endif diff --git a/Sources/MagicText/Views/ArtLetterView.swift b/Sources/MagicText/Views/ArtLetterView.swift index 45d1e59..5199120 100644 --- a/Sources/MagicText/Views/ArtLetterView.swift +++ b/Sources/MagicText/Views/ArtLetterView.swift @@ -27,7 +27,7 @@ struct ArtLetterView: View { var body: some View { let updatedSymbol = symbol == " " ? " " : symbol - let colors = DataManager.colors.map { Color($0) } + let colors = DataManager.colors.map { $0.color } let color = opacity == 1 && symbol != " " ? colors.randomElement() ?? .clear @@ -59,7 +59,7 @@ struct ArtLetterView_Previews: PreviewProvider { static var previews: some View { ArtLetterView( symbol: "K", - textColor: Color(RM.yinYang), + textColor: RM.yinYang.color, fontName: "Baskerville", fontSize: 28, delay: 0 diff --git a/Sources/MagicText/Views/BubbleView.swift b/Sources/MagicText/Views/BubbleView.swift index 72faaf7..5b857ee 100644 --- a/Sources/MagicText/Views/BubbleView.swift +++ b/Sources/MagicText/Views/BubbleView.swift @@ -32,7 +32,7 @@ struct BubbleView: View { Group { if isMask { let fontScale = fontSize * Double.random(in: 0.7 ... 0.9) - let colors = DataManager.colors.map { Color($0) } + let colors = DataManager.colors.map { $0.color } let color = blur == .zero && symbol != " " ? colors.randomElement() ?? .clear @@ -81,7 +81,7 @@ struct BubbleView_Previews: PreviewProvider { static var previews: some View { BubbleView( symbol: "K", - textColor: Color(RM.yinYang), + textColor: RM.yinYang.color, fontName: "Baskerville", fontSize: 28, isMask: true, diff --git a/Sources/MagicText/Views/CharmedLetterView.swift b/Sources/MagicText/Views/CharmedLetterView.swift index 998fd3a..0d05d13 100644 --- a/Sources/MagicText/Views/CharmedLetterView.swift +++ b/Sources/MagicText/Views/CharmedLetterView.swift @@ -82,7 +82,7 @@ struct CharmedLetterView_Previews: PreviewProvider { static var previews: some View { CharmedLetterView( symbol: "K", - textColor: Color(RM.yinYang), + textColor: RM.yinYang.color, fontName: "Baskerville", fontSize: 28, backgroundColor: .red, diff --git a/Sources/MagicText/Views/CharmedView.swift b/Sources/MagicText/Views/CharmedView.swift index b158060..018e28d 100644 --- a/Sources/MagicText/Views/CharmedView.swift +++ b/Sources/MagicText/Views/CharmedView.swift @@ -69,7 +69,7 @@ struct CharmedView_Previews: PreviewProvider { static var previews: some View { CharmedView( symbol: "K", - textColor: Color(RM.yinYang), + textColor: RM.yinYang.color, fontName: "Baskerville", fontSize: 28, isMask: true, diff --git a/Sources/MagicText/Views/FantasyView.swift b/Sources/MagicText/Views/FantasyView.swift index 933d219..8305753 100644 --- a/Sources/MagicText/Views/FantasyView.swift +++ b/Sources/MagicText/Views/FantasyView.swift @@ -32,7 +32,7 @@ struct FantasyView: View { Group { if isMask { let fontScale = fontSize * Double.random(in: 0.7 ... 0.9) - let colors = DataManager.colors.map { Color($0) } + let colors = DataManager.colors.map { $0.color } let color = blur == .zero && symbol != " " ? colors.randomElement() ?? .clear @@ -82,7 +82,7 @@ struct FantasyView_Previews: PreviewProvider { static var previews: some View { FantasyView( symbol: "K", - textColor: Color(RM.yinYang), + textColor: RM.yinYang.color, fontName: "Baskerville", fontSize: 28, isMask: true, diff --git a/Sources/MagicText/Views/MagicTextView.swift b/Sources/MagicText/Views/MagicTextView.swift index 65b1830..9795d7e 100644 --- a/Sources/MagicText/Views/MagicTextView.swift +++ b/Sources/MagicText/Views/MagicTextView.swift @@ -17,7 +17,7 @@ public struct MagicTextView: View { case artLetter case bubble case fantasy - case charmedLetter(backgroundColor: Color = Color(RM.candy)) + case charmedLetter(backgroundColor: Color = RM.candy.color) } // MARK: - Property Wrappers @@ -53,7 +53,7 @@ public struct MagicTextView: View { /// - completion: A block object to be executed when the animation sequence ends. public init( text: String, - textColors: [Color] = [Color(RM.yinYang)], + textColors: [Color] = [RM.yinYang.color], textAlignment: TextAlignment = .center, fontName: String = "Baskerville", fontSize: CGFloat, @@ -408,7 +408,7 @@ struct MagicTextView_Previews: PreviewProvider { MagicTextView( text: text, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: fontSize, minCharactersPerLine: minCharactersPerLine, magicType: .charmedLetter(backgroundColor: .blue) @@ -416,7 +416,7 @@ struct MagicTextView_Previews: PreviewProvider { MagicTextView( text: text, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: fontSize, minCharactersPerLine: minCharactersPerLine, magicType: .bubble @@ -424,7 +424,7 @@ struct MagicTextView_Previews: PreviewProvider { MagicTextView( text: text, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: fontSize, minCharactersPerLine: minCharactersPerLine, magicType: .fantasy @@ -432,7 +432,7 @@ struct MagicTextView_Previews: PreviewProvider { MagicTextView( text: text, - textColors: [Color(RM.yinYang).opacity(0.8)], + textColors: [RM.yinYang.color.opacity(0.8)], fontSize: fontSize, minCharactersPerLine: minCharactersPerLine, magicType: .artLetter From 0a0455c78cc75e1fce3b6594154ce4f13a5f805f Mon Sep 17 00:00:00 2001 From: bullinnyc Date: Wed, 12 Jun 2024 19:29:24 +0700 Subject: [PATCH 4/5] Create build-and-test.yml --- .github/workflows/build-and-test.yml | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..6e28a93 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,45 @@ +name: Build and test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build_and_test: + name: ${{ matrix.command }} on  ${{ matrix.platform }} (xcode ${{ matrix.xcode }}, ${{ matrix.macos }}) + runs-on: ${{ matrix.macos }} + strategy: + fail-fast: false + matrix: + xcode: ['15.0.1'] + macos: ['macos-13'] + scheme: ['MagicText'] + command: ['test'] + platform: ['iOS', 'macOS', 'tvOS', 'watchOS'] + steps: + - name: Switch xcode to ${{ matrix.xcode }} + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: ${{ matrix.xcode }} + - name: Double-check macOS version (${{ matrix.macos }}) + run: sw_vers + - name: Code Checkout + uses: actions/checkout@v3 + - name: Check xcodebuild version + run: xcodebuild -version + - name: Check xcode embedded SDKs + run: xcodebuild -showsdks + - name: Show buildable schemes + run: xcodebuild -list + - name: Show eligible build destinations for ${{ matrix.scheme }} + run: xcodebuild -showdestinations -scheme ${{ matrix.scheme }} + - uses: mxcl/xcodebuild@v2.0.0 + with: + platform: ${{ matrix.platform }} + scheme: ${{ matrix.scheme }} + action: ${{ matrix.command }} + code-coverage: true + verbosity: xcpretty + upload-logs: always From ccc619b8733b01c0495796af11566c6760a1907d Mon Sep 17 00:00:00 2001 From: bullinnyc Date: Wed, 12 Jun 2024 19:49:07 +0700 Subject: [PATCH 5/5] Add test plan. --- .swiftpm/MagicText.xctestplan | 24 +++++++ .../xcshareddata/xcschemes/MagicText.xcscheme | 72 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 .swiftpm/MagicText.xctestplan create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/MagicText.xcscheme diff --git a/.swiftpm/MagicText.xctestplan b/.swiftpm/MagicText.xctestplan new file mode 100644 index 0000000..7a56cf7 --- /dev/null +++ b/.swiftpm/MagicText.xctestplan @@ -0,0 +1,24 @@ +{ + "configurations" : [ + { + "id" : "276FEBCA-28BB-4ABB-B9B6-426F493EA1F8", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:", + "identifier" : "MagicTextTests", + "name" : "MagicTextTests" + } + } + ], + "version" : 1 +} diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/MagicText.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/MagicText.xcscheme new file mode 100644 index 0000000..14182c7 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/MagicText.xcscheme @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +