Swiftui Widget iOS 14 Gradient Issue

iOS 14 Medium Widget Size Background Image Refuses to Fill

Here is fixed variant - as image in the background you have to expand VStack to full screen.

Note: edgesIgnoringSafeArea allows to go beyond safe area when content is wider, but not make content wide.

var body: some View {
VStack(alignment: .leading){
Spacer()
Text("Aardvark Exactlywhat")
.font(.largeTitle)
.bold()
.padding(.bottom, 20)
.padding(.leading, 20)
.padding(.trailing, 20)
.minimumScaleFactor(0.5)
.foregroundColor(.white)
.shadow(
color: Color.black,
radius: 1.0,
x: CGFloat(4),
y: CGFloat(4))
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // << this one !!
.edgesIgnoringSafeArea(.all)
.background(
Image("plant")
.resizable()
.scaledToFill()
)
}

SwiftUI Widget background based on the value passed image url or gradient background

This took quite a while to do, because Color is not Codable, so a custom version had to be made. Here is what I got:

struct Note: Identifiable, Codable {

enum CodingKeys: CodingKey {
case title, message, background
}

let id = UUID()
let title: String
let message: String
let background: NoteBackground
}

extension Note {

enum NoteBackground: Codable {

enum NoteBackgroundError: Error {
case failedToDecode
}

case url(String)
case gradient([Color])

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

if let url = try? container.decode(String.self) {
self = .url(url)
return
}
if let gradient = try? container.decode([ColorWrapper].self) {
self = .gradient(gradient.map(\.color))
return
}

throw NoteBackgroundError.failedToDecode
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()

switch self {
case let .url(url):
try container.encode(url)
case let .gradient(gradient):
let colors = gradient.map(ColorWrapper.init(color:))
try container.encode(colors)
}
}
}
}

To make Color be Codable, it is wrapped in ColorWrapper:

enum ColorConvert {

struct Components: Codable {
let red: Double
let green: Double
let blue: Double
let opacity: Double
}

static func toColor(from components: Components) -> Color {
Color(
red: components.red,
green: components.green,
blue: components.blue,
opacity: components.opacity
)
}

static func toComponents(from color: Color) -> Components? {
guard let components = color.cgColor?.components else { return nil }
guard components.count == 4 else { return nil }
let converted = components.map(Double.init)

return Components(
red: converted[0],
green: converted[1],
blue: converted[2],
opacity: converted[3]
)
}
}

struct ColorWrapper: Codable {

let color: Color

init(color: Color) {
self.color = color
}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let components = try container.decode(ColorConvert.Components.self)
color = ColorConvert.toColor(from: components)
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let components = ColorConvert.toComponents(from: color)
try container.encode(components)
}
}

It can then be used like so:

struct ContentView: View {

let data = Note(title: "Title", message: "Message", background: .url("https://google.com"))
//let data = Note(title: "Title", message: "Message", background: .gradient([Color(red: 1, green: 0.5, blue: 0.2), Color(red: 0.3, green: 0.7, blue: 0.8)]))

var body: some View {
Text(String(describing: data))
.onAppear(perform: test)
}

private func test() {
do {
let encodedData = try JSONEncoder().encode(data)
print("encoded", encodedData.base64EncodedString())

let decodedData = try JSONDecoder().decode(Note.self, from: encodedData)
print("decoded", String(describing: decodedData))
} catch let error {
fatalError("Error: \(error.localizedDescription)")
}
}
}

Note: the Color you encode cannot be something like Color.red - it has to be made from the RGB components like using the Color(red:green:blue:) initializer.

For you, you could do something like this to change the background depending on entry's background:

@ViewBuilder func bg() -> some View {
switch entry.background {
case let .url(url):
NetworkImage(url: URL(string: url))
case let .gradient(colors):
LinearGradient(
gradient: Gradient(colors: colors),
startPoint: .top,
endPoint: .bottom
)

/// CAN ADD ANOTHER CASE TO `NoteBackground` ENUM FOR SOLID COLOR HERE
}
}

Widget link works but doesn't do anything

I found out. To make some changes in your viewController you need to make it with rootViewController:

private func getURL(urlContext: Set<UIOpenURLContext>) {
guard let urlContexts = urlContext.first(where: { $0.url.scheme == "app-deeplink" }) else { return }
print(urlContexts.url)
print("success")
let rootViewController = window?.rootViewController as? ViewController
rootViewController?.cameFromWidget()
}


Related Topics



Leave a reply



Submit