SwiftUI initializing values for a view - view

I have this code:
// Border Color
#State private var activeWindowBorderColorText: String = UserDefaults.standard.string(forKey: "ActiveWindowBorderColor") ?? "775759"
let red = $activeWindowBorderColorText[1..<2]
#State private var activeWindowBorderColor = Color(.sRGB, red: red, green: 1, blue: 1)
What must I do to get the red, green and blue component from the string as parameters for the Color()?
So, how to get the substrings?
I tried the extension from here:
How does String substring work in Swift
but it gives me:
Cannot use instance member '$activeWindowBorderColorText' within property initializer; property initializers run before 'self' is available

How about computed properties?
var red: String { activeWindowBorderColorText[1..<2] }
They update with the state.

Thank you both for the reply, I got it to work with a combination of .onAppear() and computed properties.
The conversion looks ugly, but it works :-)
.onAppear() {
print("initial color: \(activeWindowBorderColorText)")
var redHex: String { activeWindowBorderColorText[4..<6] }
let redDec = Int(redHex, radix:16)
let red = Double(redDec ?? 1)
var greenHex: String { activeWindowBorderColorText[6..<8] }
let greenDec = Int(greenHex, radix:16)
let green = Double(greenDec ?? 1)
var blueHex: String { activeWindowBorderColorText[8..<10] }
let blueDec = Int(blueHex, radix:16)
let blue = Double(blueDec ?? 1)
print("color: \(activeWindowBorderColorText) redHex: \(redHex ) redDec: \(redDec ?? 1) red:\(red)")
print("color: \(activeWindowBorderColorText) greenHex: \(redHex ) greenDec: \(redDec ?? 1) green:\(green)")
print("color: \(activeWindowBorderColorText) blueHex: \(redHex ) blueDec: \(redDec ?? 1) blue:\(blue)")
activeWindowBorderColor = Color(.sRGB, red: red, green: green, blue: blue)
}

Related

Change UI color based on a condition in Xcode with SwiftUI

I am trying to change the color on animation applied on a swipe gesture but its not working. Is there any logical error that i am unable to find ? kindly help.
the if condition that i am using
import SwiftUI
struct ContentView: View {
#StateObject var manager = RecipeManager()
#Namespace private var viewSpace
var body: some View {
ZStack {
if manager.currentRecipeIndex % 2 == 0 {
Color.lightBackground
.ignoresSafeArea()
.transition(.move(edge: .bottom))
} else {
Color.darkBackground
.ignoresSafeArea()
.transition(.move(edge: .bottom))
}
RecipeOverview(manager: manager, viewSpace: viewSpace)
.padding(.horizontal)
if manager.selectedRecipe != nil {
RecipeDetailView(manager: manager, viewSpace: viewSpace)
}
}
}
}
and here is the extension to the color
extension Color {
static let lightBackground = Color.init(red: 243/255, green: 243/255, blue: 243/255)
static let darkBackground = Color.init(red: 34/255, green: 51/255, blue: 68/255)
}
The program keeps showing the white screen. but if change the if condition to show the darkBackground then it does not show the white screen while executing the else statement.

View Modifier messing with animations

I've been looking to add a loading indicator to my project, and found a really cool animation here. To make it easier to use, I wanted to incorporate it into a view modifier to put it on top of the current view. However, when I do so, it doesn't animate when I first press the button. I have played around with it a little, and my hypothesis is that the View Modifier doesn't pass the initial isAnimating = false, so only passes it isAnimating = true when the button is pressed. Because the ArcsAnimationView doesn't get the false value initially, it doesn't actually animate anything and just shows the static arcs. However, if I press the button a second time afterwards, it seems to be initialized and the view properly animates as desired.
Is there a better way to structure my code to avoid this issue? Am I missing something key? Any help is greatly appreciated.
Below is the complete code:
import SwiftUI
struct ArcsAnimationView: View {
#Binding var isAnimating: Bool
let count: UInt = 4
let width: CGFloat = 5
let spacing: CGFloat = 2
init(isAnimating: Binding<Bool>) {
self._isAnimating = isAnimating
}
var body: some View {
GeometryReader { geometry in
ForEach(0..<Int(count)) { index in
item(forIndex: index, in: geometry.size)
// the rotation below is what is animated ...
// I think the problem is that it just starts at .degrees(360), instead of
// .degrees(0) as expected, where it is then animated to .degrees(360)
.rotationEffect(isAnimating ? .degrees(360) : .degrees(0))
.animation(
Animation.default
.speed(Double.random(in: 0.05...0.25))
.repeatCount(isAnimating ? .max : 1, autoreverses: false)
, value: isAnimating
)
.foregroundColor(Color(hex: AppColors.darkBlue1.rawValue))
}
}
.aspectRatio(contentMode: .fit)
}
private func item(forIndex index: Int, in geometrySize: CGSize) -> some View {
Group { () -> Path in
var p = Path()
p.addArc(center: CGPoint(x: geometrySize.width/2, y: geometrySize.height/2),
radius: geometrySize.width/2 - width/2 - CGFloat(index) * (width + spacing),
startAngle: .degrees(0),
endAngle: .degrees(Double(Int.random(in: 120...300))),
clockwise: true)
return p.strokedPath(.init(lineWidth: width))
}
.frame(width: geometrySize.width, height: geometrySize.height)
}
}
struct ArcsAnimationModifier: ViewModifier {
#Binding var isAnimating: Bool
func body(content: Content) -> some View {
ZStack {
if isAnimating {
ArcsAnimationView(isAnimating: _isAnimating)
.frame(width: 150)
}
content
.disabled(isAnimating)
}
}
}
extension View {
func loadingAnimation(isAnimating: Binding<Bool>) -> some View {
self.modifier(ArcsAnimationModifier(isAnimating: isAnimating))
}
}
Here is where I actually call the function:
struct AnimationView: View {
#State var isAnimating = false
var body: some View {
VStack {
Button(action: {
self.isAnimating = true
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
self.isAnimating = false
}
}, label: {
Text("show animation")
})
}
.loadingAnimation(isAnimating: $isAnimating)
}
}
Note: I am fairly certain the issue is with View Modifier since if I call ArcsAnimationView as a regular view in AnimationView, it works as expected.
I get there to see some implementation, but I think others would prefer a simple base to start from.
here my 2 cents to show how to write an AnimatableModifier that can be used on multiple objects cleaning up ".animation" in code.
struct ContentView: View {
#State private var hideWhilelUpdating = false
var body: some View {
Image(systemName: "tshirt.fill")
.modifier(SmoothHideAndShow(hide: hideWhilelUpdating))
Text("Some contents to show...")
.modifier(SmoothHideAndShow(hide: hideWhilelUpdating))
Button( "hide and show smootly") {
hideWhilelUpdating.toggle()
}
.padding(60)
}
}
struct SmoothHideAndShow: AnimatableModifier {
var hide: Bool
var animatableData: CGFloat {
get { CGFloat(hide ? 0 : 1) }
set { hide = newValue == 0 }
}
func body(content: Content) -> some View {
content
.opacity(hide ? 0.2 : 1)
.animation(.easeIn(duration: 1), value: hide)
}
}
when pressing button, our bool will trigger animation that fades in and out our text.
I use it during network calls (omitted for clarity... and replaced with button) to hide values under remote update. When network returns, I toggle boolean.

Why does this gradient view crash after some time?

So I have this custom view I made to make rotating gradient. I do it this way because it's the only way I know how to get the animation I like properly. The issue that after around 10 minutes of using the app or so, it'll crash and I'm not sure why and I do not know how to resolve it.
struct Background: View {
/// Style of gradient animation rotation
var style: Style = .normal
#State var start = UnitPoint.leading
#State var end = UnitPoint.trailing
var timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
#State var colors: [Color] = [ .blue, .red ]
var body: some View {
LinearGradient(gradient: Gradient(colors: colors ), startPoint: start, endPoint: end)
.animation(Animation.easeInOut(duration: 1.5).repeatForever(), value: start) /// don't forget the `value`!
.onReceive(timer) { _ in
self.start = nextPointFrom(self.start)
self.end = nextPointFrom(self.end)
}
.edgesIgnoringSafeArea(.all)
}
/// cycle to the next point
func nextPointFrom(_ currentPoint: UnitPoint) -> UnitPoint {
switch currentPoint {
case .top:
return .topTrailing
case .topLeading:
return .top
case .leading:
return .topLeading
case .bottomLeading:
return .leading
case .bottom:
return .bottomLeading
case .bottomTrailing:
return .bottom
case .trailing:
return .bottomTrailing
case .topTrailing:
return .trailing
default:
print("Unknown point")
return .top
}
}
}
Here is how it may be used
struct ContentView: View {
var body: some View {
ZStack {
Background()
OtherViews()
}
}
}
Here is the error I get after around 10 minutes of it running but I don't know what it is
By my testing it leaks memory, so this might be a reason, try the following modified part - animation duration set to same as timer interval but removed repeating, because it is anyway restarted on update:
LinearGradient(gradient: Gradient(colors: colors ), startPoint: start, endPoint: end)
.animation(Animation.easeInOut(duration: 2), value: start) // << here !!

Random SKColor in Swift

I was wondering how I would be able to choose a random SKColor for a node out of Red, Blue or Green color?
At the moment, I am just setting the Node color by block.fillColor = SKColor.redColor()
Thanks :)
You can use create with red/green/blue:
SKColor(red: Float(arc4random_uniform(255))/255.0,
green: Float(arc4random_uniform(255))/255.0 ,
blue: Float(arc4random_uniform(255))/255.0 ,
alpha: 1.0)
This will generate a random color. You can use the same principle in the HSV space....
Another version if you create a randomTo1() func which return a CGFloat random number from 0 to 1:
func randomTo1() -> CGFloat{
return CGFloat(arc4random_uniform(255))/255.0
}
let color=SKColor(red: randomTo1(),green: randomTo1(), blue: randomTo1(), alpha:1.0)
Here is a solution for RGB and HSB:
func random() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
func trulyRandomColor() -> SKColor {
return SKColor(red: random(), green: random(), blue: random(), alpha: 1.0)
}
func niceRandomColor() -> SKColor {
return SKColor(hue: random(), saturation: 1.0, brightness: 1.0, alpha: 1.0)
}
EDIT: Something like this works if you only need certain colors and want to choose randomly between them:
import GameKit
struct ColorGenerator {
private let distribution : GKRandomDistribution
private let possibleColors = [
SKColor.redColor(),
SKColor.blueColor(),
SKColor.greenColor(),
SKColor.purpleColor(),
SKColor.yellowColor(),
SKColor.brownColor(),
SKColor.whiteColor()
]
init() {
distribution = GKShuffledDistribution(lowestValue: 0, highestValue: possibleColors.count - 1)
}
func random() -> SKColor {
return possibleColors[distribution.nextInt()]
}
}
let generator = ColorGenerator()
for _ in 1...100 {
generator.random()
}
I used GKShuffledDistribution here to avoid colors being repeated and to make it look more "random" (even though it's not). You could also replace it with GKRandomDistribution, but then you'd maybe have 5 times the same colour in a row which isn't very desirable usually.

set and create paragraph style in swift

I am trying to port an objective C method to draw text in a PDF context to Swift.
I could convert most of the code, but the following lines are giving me a problem.
Some help in the conversion would be welcome.
Here the Objective C code:
-(void)drawText:(NSString *)textToDraw context:(CGContextRef)myPDFContext textcolor:(NSColor *)textcolor textfont:(NSFont*)textfont textbox:(CGRect)boxsize pagesize:(CGSize)pageSize {
// .........
// create paragraph style and assign text alignment to it
CTTextAlignment alignment = kCTJustifiedTextAlignment;
CTParagraphStyleSetting _settings[] = { {kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment} };
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(_settings, sizeof(_settings) / sizeof(_settings[0]));
// set paragraph style attribute
CFAttributedStringSetAttribute(attrStr, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTParagraphStyleAttributeName, paragraphStyle);
// .........
}
// The following lines are my try in Swift, but this gives errors:
func DrawText(textToDraw:String, myPDFContext:CGContextRef, textcolor:NSColor, textfont:NSFont, boxsize:CGRect, pagesize:CGSize) {
var alignment = CTTextAlignment.TextAlignmentLeft
let alignmentSetting = CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.Alignment, valueSize: sizeof(alignment), value: &alignment)
let paragraphStyle = CTParagraphStyleCreate(alignmentSetting, 1)
//.......
If this is solved I could post the complete method in Swift.
I think this does it:
var alignment = CTTextAlignment.TextAlignmentLeft
let alignmentSetting = [CTParagraphStyleSetting(spec: .Alignment, valueSize: UInt(sizeofValue(alignment)), value: &alignment)]
let paragraphStyle = CTParagraphStyleCreate(alignmentSetting, 1)
CFAttributedStringSetAttribute(attrStr, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTParagraphStyleAttributeName, paragraphStyle)
I made the following modifications:
I changed sizeof(alignment) to UInt(sizeofValue(alignment)) because sizeof in Swift only takes a type (such as sizeof(Int)). The UInt() is a constructor that turns the Int returned by sizeofValue into the UInt needed by this call.
I make alignmentSetting into an array so that it could be passed to an UnsafePointer of that type. This also better matches the original version.
I changed CTParagraphStyleSpecifier.Alignment to just .Alignment since the first part isn't needed since spec is of type CTParagraphStyleSpecifier.
The hardcoded 1 is OK in this case because you are passing just one value, but the more general solution would be to do:
let paragraphStyle = CTParagraphStyleCreate(alignmentSetting, UInt(alignmentSetting.count))
Here the full working method based on the corrections and help.
func drawText(textToDraw:String, myPDFContext:CGContextRef, textcolor:NSColor, textfont:NSFont, boxsize:CGRect, pagesize:CGSize) {
let _font = NSFont(name:textfont.fontName, size:textfont.pointSize )
if (_font == nil) {
println("Font [\(textfont)] does not exist")
return
}
let myBoxWidth = boxsize.size.width
let myBoxHeight = boxsize.size.height
let myBoxxpos = boxsize.origin.x
let myBoxypos = boxsize.origin.y
let frameRect = CGRectMake(myBoxxpos, myBoxypos,myBoxWidth,myBoxHeight);
let framePath = CGPathCreateMutable()
CGPathAddRect(framePath, nil, frameRect);
// create attributed string
let attrStr = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attrStr, CFRangeMake(0, 0), textToDraw);
// create font
let font = CTFontCreateWithName(textfont.fontName, textfont.pointSize, nil);
var alignment = CTTextAlignment.TextAlignmentLeft
let alignmentSetting = [CTParagraphStyleSetting(spec: .Alignment, valueSize: UInt(sizeofValue(alignment)), value: &alignment)]
//let paragraphStyle = CTParagraphStyleCreate(alignmentSetting, 1)
let paragraphStyle = CTParagraphStyleCreate(alignmentSetting, UInt(alignmentSetting.count))
CFAttributedStringSetAttribute(attrStr, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTParagraphStyleAttributeName, paragraphStyle)
// set font attribute
CFAttributedStringSetAttribute(attrStr, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTFontAttributeName, font);
// set color attribute
CFAttributedStringSetAttribute(attrStr,CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTForegroundColorAttributeName,textcolor.CGColor);
// Prepare the text using a Core Text Framesetter.
let framesetter = CTFramesetterCreateWithAttributedString(attrStr);
// Get the frame that will do the rendering.
let currentRange = CFRangeMake(0, 0);
let frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, nil);
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(myPDFContext, CGAffineTransformIdentity);
// Draw the frame.
CTFrameDraw(frameRef, myPDFContext);
}

Resources