How to compare three Color in SwiftUI - if-statement

I have three Colors and I want to compare them. Should I make Color conform to Equatable protocol?
Code :
if Color.red == Color.green == Color.blue {
// do something
}

I really dont undestand what you want to achieve but you can compare them like
let colorr = UIColor(red: 122/255, green: 100/255, blue: 180/255, alpha: 1.0)
let rgbColorr = colorr.cgColor
let rgbColorrs = rgbColorr.components
This rgbColorrs prints and array like [0.47843137254901963, 0.39215686274509803, 0.7058823529411765, 1.0] its like [red,green,blue,alpha]
if rgbColorrs[0] == rgbColors[1] == rgbColors[2]{
//....
}
Or if you want to compare equality
if UIColor.red.isEqual(UIColor.green.isEqual(UIColor.blue)){
...
}

Related

Circle().scaleEffect() In ForEach Won't Draw If Scale Is Less Than 1 With A Computed Scale

Can someone tell me why this code does not return four circles in a VStack, with the largest being full size, and the smallest at 25% of full size? How do I get this without using four separate circles? And no, it doesn't work with the .frame() inside the ForEach
struct MultipleCirclesView: View {
let maxSize: CGFloat = 100
let count: Int = 4
let color: Color = Color.red
let borderLineWidth: CGFloat = 8
var body: some View {
VStack {
ForEach(0..<count) { current in
Circle()
.strokeBorder(color, lineWidth: borderLineWidth)
.scaleEffect(CGFloat((current + 1) / count))
}
.frame(width: maxSize, height: maxSize)
}
}
}
Here is a fix - type fixed:
Circle()
.strokeBorder(color, lineWidth: borderLineWidth)
.scaleEffect(CGFloat(current + 1) / CGFloat(count)) // << here !!
Note: scaleEffect does not change view's frame - only drawing.

SwuifUI transitions for parameterized list

In learning SwiftUI I'm trying to do transition animations I used with UIKit. I sometimes used pages with tables that changed with a parameter by using a containerView and transitioning between children. The SwiftUI equivalent seems to be a single List View dependent on the parameter. But it seems that .transitions don't work well since no view is being removed/inserted in the hierarchy (and simple animations don't give me the transition options I want). The only way I have made it work at all is by "manually" removing the old List and inserting the new one, but that seems a kludge and didn't work perfectly. Here's a toy version of the code that illustrated the problem -- the inserted green rectangle is just for comparison -- the transition works fine for it but not for the List.
struct ContentView: View {
let listItems:[[String]] = [["A0","B0"],["A1","B1"]]
#State var pointer:Int = 0
var body: some View {
VStack{
List(listItems[pointer], id:\.self)
{item in Text(item)
.foregroundColor(self.pointer == 0 ? Color.blue : Color.purple)
}
.transition(.offset(x: -600, y: 0))
if pointer == 1 {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 200)
.foregroundColor(.green)
.transition(.offset(x: 500, y: 300))
}
}
.onTapGesture {
withAnimation(.easeInOut(duration: 1)){
self.pointer = (self.pointer + 1) % 2
}
}
}
}
I've found a partial solution, but it behaves strangely. Instead of a single List with varying content, transitions seem to require separate lists which are inserted/removed. Here is a simple implementation
struct ContentView: View {
let listItems:[[String]] = [["A0","B0"],["A1","B1"],["A2","B2"]]
#State var pointer:Int = 0
var body: some View {
ZStack{ForEach(0...2, id: \.self)
{index in
ZStack{
if index == self.pointer {
List(self.listItems[index], id:\.self)
{item in HStack{Text(item);Spacer()}
.foregroundColor(self.pointer == 0 ? Color.blue : Color.purple)
.frame(height:20)
.padding(2)
.background(Color.yellow)
}
.transition(.asymmetric(insertion: .offset(x: 400, y: -370), removal: .offset(x: 0, y: -870)))
}
}
}
}
.onTapGesture {
withAnimation(.easeInOut(duration: 1)){
self.pointer = (self.pointer + 1) % 3
}
}
But it responds oddly to choice of offsets -- I don't understand the -370 y-offset needed to make insertions come in horizontally -- it was found by trial/error. And the transition 2->0 is different from the other two.

Having Problems Setting View Background Color with List View--SwiftUI

I want to set the View Background color to green. I have a List View and it's fine but the underlying view needs to be green--see image. I know I'm missing something but with SwiftUI you sometimes don't know if it's a bug or just how the thing is supposed to work. Any ideas that have worked for others I would appreciate. I have attached my code and I am on Xcode Version 11.5 (11E608c).
Here is the code:
var body: some View {
ZStack() {
Color(red: 95.0/255.0, green: 122.0/255.0, blue: 128.0/255)
.edgesIgnoringSafeArea(.all)
Text("")
List {
Group {
Text("Price: ")
Text("Down: ")
Text("APR: ")
Text("Term: ")
Text("Tax: ")
Text("Rebate: ")
}.listRowBackground(Color(red: 95.0/255.0, green: 122.0/255.0, blue: 128.0/255))
Group {
Text("Add On: ")
Text("Fees: ")
Text("Trade Value: ")
Text("Total Interest: ")
Text("Payment: ")
}.listRowBackground(Color(red: 95.0/255.0, green: 122.0/255.0, blue: 128.0/255))
}
}
}
Unfortunately swiftui doesn't support this yet, however, List is just a uitableview under the hood so you can just change the UITableView properties.
struct StackOverflow1: View {
// you need to modify the UItableview in the initializer
init() {
UITableView.appearance().backgroundColor = .yellow // Change background color
UITableViewCell.appearance().backgroundColor = .green // Change each cell's background color
}
var body: some View {
// your code goes here
}
}
Its important to note that this will change all List instances in this view so if you have multiple Lists they all will be the same colors.
to overcome this issue you can make different views for each List and then embed them in your main view.

How to make my objects in a VStack mask to different full screen size gradients in swiftUI?

Inside of a ZStack:
I know you can set up a VStack inside a .mask(), but then I have to add offsets to each object so they don't overlap.
Is there a way to modify your object to mask a specific view inside a single VStack?
So you probably want either the left column or the right column of this demo:
In the left column, the gradient spans all of the bubbles, including the bubbles off the screen. So the gradient appears to scroll with the bubbles.
In the right column, the gradient spans just the visible frame of the scroll view, so the bubble backgrounds appear to change as the bubbles move up and down.
Either way, this is a tricky problem! I've solved it before for UIKit. Here's a solution for SwiftUI.
Here's what we'll do:
Draw each “bubble” (rounded rectangle) with a clear (transparent) background.
Record the frame (in global coordinates) of each bubble in a “preference”. A preference is SwiftUI's API for passing values from child views to ancestor views.
Add a background to some common ancestor of all the bubble views. The background draws the gradient big enough to cover the entire common ancestor, but masked to only be visible in a rounded rectangle under each bubble.
We'll collect the frames of the bubbles in this data structure:
struct BubbleFramesValue {
var framesForKey: [AnyHashable: [CGRect]] = [:]
var gradientFrame: CGRect? = nil
}
We'll collect the frames of the bubbles in the framesForKey property. Since we want to draw two gradients (gold and teal), we need to keep separate collections of bubble frames. So framesForKey[gold] collects the frames of the gold bubbles, and framesForKey[teal] collects the frames of the teal bubbles.
We'll also need the frame of the common ancestor, so we'll store that in the gradientFrame property.
We'll collect these frames using the preference API, which means we need to define a type that conforms to the PreferenceKey protocol:
struct BubbleFramesKey { }
extension BubbleFramesKey: PreferenceKey {
static let defaultValue: BubbleFramesValue = .init()
static func reduce(value: inout BubbleFramesValue, nextValue: () -> BubbleFramesValue) {
let next = nextValue()
switch (value.gradientFrame, next.gradientFrame) {
case (nil, .some(let frame)): value.gradientFrame = frame
case (_, nil): break
case (.some(_), .some(_)): fatalError("Two gradient frames defined!")
}
value.framesForKey.merge(next.framesForKey) { $0 + $1 }
}
}
Now we can define two new methods on View. The first method declares that the view should be a bubble, meaning it should have a rounded rect background that shows the gradient. This method uses the preference modifier and a GeometryReader to supply its own frame (in global coordinates) as a BubbleFramesValue:
extension View {
func bubble<Name: Hashable>(named name: Name) -> some View {
return self
.background(GeometryReader { proxy in
Color.clear
.preference(
key: BubbleFramesKey.self,
value: BubbleFramesValue(
framesForKey: [name: [proxy.frame(in: .global)]],
gradientFrame: nil))
})
}
}
The other method declares that the view is the common ancestor of bubbles, and so it should define the gradientFrame property. It also inserts the background gradient behind itself, with the appropriate mask made of RoundedRectangles:
extension View {
func bubbleFrame(
withGradientForKeyMap gradientForKey: [AnyHashable: LinearGradient]
) -> some View {
return self
.background(GeometryReader { proxy in
Color.clear
.preference(
key: BubbleFramesKey.self,
value: BubbleFramesValue(
framesForKey: [:],
gradientFrame: proxy.frame(in: .global)))
} //
.edgesIgnoringSafeArea(.all))
.backgroundPreferenceValue(BubbleFramesKey.self) {
self.backgroundView(for: $0, gradientForKey: gradientForKey) }
}
private func backgroundView(
for bubbleDefs: BubbleFramesKey.Value,
gradientForKey: [AnyHashable: LinearGradient]
) -> some View {
return bubbleDefs.gradientFrame.map { gradientFrame in
GeometryReader { proxy in
ForEach(Array(gradientForKey.keys), id: \.self) { key in
bubbleDefs.framesForKey[key].map { bubbleFrames in
gradientForKey[key]!.masked(
toBubbleFrames: bubbleFrames, inGradientFrame: gradientFrame,
readerFrame: proxy.frame(in: .global))
}
}
}
}
}
}
We set up the gradient to have the correct size, position, and mask in the masked(toBubbleFrames:inGradientFrame:readerFrame:) method:
extension LinearGradient {
fileprivate func masked(
toBubbleFrames bubbleFrames: [CGRect],
inGradientFrame gradientFrame: CGRect,
readerFrame: CGRect
) -> some View {
let offset = CGSize(
width: gradientFrame.origin.x - readerFrame.origin.x,
height: gradientFrame.origin.y - readerFrame.origin.y)
let transform = CGAffineTransform.identity
.translatedBy(x: -readerFrame.origin.x, y: -readerFrame.origin.y)
var mask = Path()
for bubble in bubbleFrames {
mask.addRoundedRect(
in: bubble,
cornerSize: CGSize(width: 10, height: 10),
transform: transform)
}
return self
.frame(
width: gradientFrame.size.width,
height: gradientFrame.size.height)
.offset(offset)
.mask(mask)
}
}
Whew! Now we're ready to try it out. Let's write the ContentView I used for the demo at the top of this answer. We'll start by defining a gold gradient and a teal gradient:
struct ContentView {
init() {
self.gold = "gold"
self.teal = "teal"
gradientForKey = [
gold: LinearGradient(
gradient: Gradient(stops: [
.init(color: Color(#colorLiteral(red: 0.9823742509, green: 0.8662455082, blue: 0.4398147464, alpha: 1)), location: 0),
.init(color: Color(#colorLiteral(red: 0.3251565695, green: 0.2370383441, blue: 0.07140993327, alpha: 1)), location: 1),
]),
startPoint: UnitPoint(x: 0, y: 0),
endPoint: UnitPoint(x: 0, y: 1)),
teal: LinearGradient(
gradient: Gradient(stops: [
.init(color: Color(#colorLiteral(red: 0, green: 0.8077999949, blue: 0.8187007308, alpha: 1)), location: 0),
.init(color: Color(#colorLiteral(red: 0.08204867691, green: 0.2874087095, blue: 0.4644176364, alpha: 1)), location: 1),
]),
startPoint: UnitPoint(x: 0, y: 0),
endPoint: UnitPoint(x: 0, y: 1)),
]
}
private let gold: String
private let teal: String
private let gradientForKey: [AnyHashable: LinearGradient]
}
We'll want some content to show in the bubbles:
extension ContentView {
private func bubbledItem(_ i: Int) -> some View {
Text("Bubble number \(i)")
.frame(height: 60 + CGFloat((i * 19) % 60))
.frame(maxWidth: .infinity)
.bubble(named: i.isMultiple(of: 2) ? gold : teal)
.padding([.leading, .trailing], 20)
}
}
Now we can define the body of ContentView. We draw VStacks of bubbles, each inside a scroll view. On the left side, I put the bubbleFrame modifier on the VStack. On the right side, I put the bubbleFrame modifier on the ScrollView.
extension ContentView: View {
var body: some View {
HStack(spacing: 4) {
ScrollView {
VStack(spacing: 8) {
ForEach(Array(0 ..< 20), id: \.self) { i in
self.bubbledItem(i)
}
} //
.bubbleFrame(withGradientForKeyMap: gradientForKey)
} //
ScrollView {
VStack(spacing: 8) {
ForEach(Array(0 ..< 20), id: \.self) { i in
self.bubbledItem(i)
}
}
} //
.bubbleFrame(withGradientForKeyMap: gradientForKey)
}
}
}
And here's the PreviewProvider so we can see it in Xcode's canvas:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI: Apply a linear gradient to a shape using a Custom Class

How do I apply a linear gradient with a custom class that I made on a shape? For some reason it is not working for me. The foregroundColor is not working for me.
RoundedRectangle(cornerRadius: 30)
.frame(width: geometry.size.width * 1.01, height:geometry.size.height * 0.23)
.rotationEffect(.degrees(12), anchor: .center)
.foregroundColor(LinearGradient (gradient: Gradient(colors:[Color(ColorsSaved.gitLabDark),Color(ColorsSaved.gitLabLight)]),startPoint: .leading,endPoint: .trailing))
I created my CustomClass in a Swift file. Please see code below for that.
import Foundation
import UIKit
struct ColorsSaved {
static let gitLabDark = UIColor( red: 0.12, green: 0.08, blue: 0.22, alpha: 1.0)
static let gitLabLight = UIColor( red: 0.26, green: 0.2, blue: 0.44, alpha: 1.0)
static let neonGreen = UIColor(red: 0.497, green: 0.738, blue: 0.35, alpha: 1)
static let darkGreenTorquise = UIColor(red: 0.242, green: 0.683, blue: 0.577, alpha: 1)
static let brightOrange = UIColor(red: 0.917, green: 0.469, blue: 0.218, alpha: 1)
If I correctly understood your intention, I would go with the following approach.
Result demo:
Code:
GeometryReader { geometry in
LinearGradient(gradient:
Gradient(colors:
[Color(ColorsSaved.gitLabDark), Color(ColorsSaved.gitLabLight)]),
startPoint: .leading, endPoint: .trailing)
.frame(width: geometry.size.width * 1.01, height:geometry.size.height * 0.23)
.mask(RoundedRectangle(cornerRadius: 30))
.rotationEffect(.degrees(12), anchor: .center)
}
Gradients in SwiftUI are supposed to be added with the help of ZStack. As you haven't added much code in your question I made my assumptions. Try this in your custom view:
struct WelcomeView: View {
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: [.pink, .purple]), startPoint: .leading, endPoint: .trailing)
// here goes your other view component
Text("Welcome, John")
}
}
}