I have a widget view looking like this:
struct WidgetEntryView: View {
var entry: Provider.Entry
#Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall:
ZStack {
VStack(spacing: 12) {
// ...
}
.padding(10)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.background(Color.red.edgesIgnoringSafeArea(.all))
case .systemMedium:
ZStack {
VStack(spacing: 12) {
// ...
}
.padding(10)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.background(Color.blue.edgesIgnoringSafeArea(.all))
default:
ZStack {
VStack(spacing: 12) {
// ...
}
.padding(10)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.background(Color.black.edgesIgnoringSafeArea(.all))
}
}
}
The widget supports all 3 main size families:
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
WidgetEntryView(entry: entry)
}
.configurationDisplayName("MyWidget")
.description("...")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
And here's my PreviewProvider:
struct Widget_Previews: PreviewProvider {
static var previews: some View {
Group {
WidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent())
.previewContext(WidgetPreviewContext(family: .systemSmall))
WidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent())
.previewContext(WidgetPreviewContext(family: .systemMedium))
WidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent())
.previewContext(WidgetPreviewContext(family: .systemLarge))
}
}
}
So I have a preview for each size family on the canvas, but for some reason, all of them are rendered with a blue background. Or in other words, all of them are rendered as a .systemMedium family. When I actually run the widget on the simulator, it has the correct look. Why does the preview always skip to the .systemMedium case and ignores the other ones? How can I fix this?
The #Environment var did not work for previews.
But you can use an environment modifier like this:
YourWidgetView()
.previewContext(WidgetPreviewContext(family: .systemSmall))
.environment(\.widgetFamily, .systemSmall)
But you have to write an EnvironmentKey extension. This is the solution i used:
How to set widgetFamily environment
Related
I'm developing a simple contacts application, when adding the lines shown below, I receive the error: Argument passed to call that takes no arguments.
let websitetext: String
init(){
self.websitetext = contact.website
}
Below is the entire file with irrelevant data removed.
import Foundation
import SwiftUI
struct ContactsDetailView: View{
let contact: Contact
let websitetext: String //lines referenced
init(){
self.websitetext = contact.website
}
var body: some View {
HStack{
VStack {
Image(contact.imageName)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipped()
.cornerRadius(50)
Text(contact.name)
.font(.system(size: 21, weight:.medium, design: .default))
Form{
HStack {
Text ("Phone")
Spacer()
Text(contact.phone)
.foregroundColor(.gray)
.font(.callout)
}
Section{
Link("Website", destination: URL(string: websitetext)!) //Uses the website text variable
}
}
}
}
}
}
struct ResourcesDetailView_Previews: PreviewProvider {
static var previews: some View {
ContactsDetailView(contact: contacts[0]) //ERROR LINE
}
}
Any help to solve this issue would be appreciated!
I want the Images to change according to the text, but it works over .degisim String and int value in the ready-made code block I found. That's why it doesn't work for String.
import SwiftUI
struct altin: View {
#State var users: [Altin] = []
var body: some View {
ZStack{
List(users) { altin in
NavigationLink {
detayView(altinKur: altin)
} label: {
HStack{
Image(systemName: getImage(percent: Int(altin.degisim)))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 9, height: 9)
.foregroundColor(
(altin.degisim).contains("-") ?
Color.down :
Color.up)
.padding(.trailing, 5)
Text(altin.degisim)
.font(.system(size: 16))
.fontWeight(.medium)
.foregroundColor(
(altin.degisim).contains("-") ?
Color.down :
Color.up)
}
}
}
}
.listStyle(PlainListStyle())
.padding(0)
.onAppear {
apiCall().getUsers { (users) in
self.users = users
}
}
}
}
struct altin_Previews: PreviewProvider {
static var previews: some View {
altin()
}
}
func getImage(percent: Int) -> String{
if percent > 0 {
return "chevron.up"
}else{
return "chevron.down"
}
}
.foregroundcolor works but not within Image.
The code is more complex, I simplified it for solution.
Model:
struct Altin: Codable, Identifiable {
let id = UUID()
let name: String
let alis: String
let satis: String
let degisim: String
}
Playing with SwiftUI and WidgetKit recently and faced a nasty problem. My widget seems to be working in the SwiftUI Canvas but it is completely empty when built onto a simulator or device.
Images:
In SwiftUI canvas:
https://github.com/beanut/images/blob/main/Screenshot%202020-12-19%20at%204.00.57%20PM.png?raw=true
When built onto the device:
https://github.com/beanut/images/blob/main/Screenshot%202020-12-19%20at%204.01.13%20PM.png?raw=true
My code:
'''
import WidgetKit
import SwiftUI
import Intents
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
var entries = [SimpleEntry]()
let currentDate = Date()
let midnight = Calendar.current.startOfDay(for: currentDate)
let nextMidnight = Calendar.current.date(byAdding: .day, value: 1, to: midnight)!
//To refresh timeline every min
for offset in 0 ..< 60 * 24 {
let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: midnight)!
entries.append(SimpleEntry(date: entryDate))
}
let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}
struct WidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
HStack() {
VStack {
Spacer()
VStack(alignment: .leading) {
Text(DateManager().getDayOfWeekInString(date: entry.date)!)
.font(Font(UIFont(name: "HoeflerText-Italic", size: 44)!))
.foregroundColor(.white)
.multilineTextAlignment(.trailing)
.opacity(1)
Text("\(DateManager().getDayAsString(date: entry.date)) \(DateManager().monthAsString(date: entry.date))")
.font(Font(UIFont(name: "Copperplate", size: 26)!))
.multilineTextAlignment(.trailing)
.opacity(1)
.foregroundColor(.gray)
}
}
.padding(.leading, 20)
.padding(.bottom, 20)
Spacer()
VStack(alignment: .trailing, spacing: -20) {
Text(DateManager().getHour(date: entry.date))
.font(Font(UIFont(name: "Copperplate-Bold", size: 86)!))
.foregroundColor(.white)
.multilineTextAlignment(.trailing)
.opacity(1)
Text(DateManager().getMinuteWithTwoDigits(date: entry.date))
.font(Font(UIFont(name: "Copperplate-Bold", size: 86)!))
.multilineTextAlignment(.trailing)
.opacity(1)
.foregroundColor(.gray)
}
.padding(.trailing, 15)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Image(uiImage: #imageLiteral(resourceName: "moon")), alignment: .center)
}
}
#main
struct Widget: SwiftUI.Widget {
let kind: String = "Widget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
WidgetEntryView(entry: entry)
}
.configurationDisplayName("TestWidget")
.description("This is an example widget.")
}
}
struct Widget_Previews: PreviewProvider {
static var previews: some View {
WidgetEntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemMedium))
}
}
'''
**Update
I noticed that the widget does show if I remove a VStack. But it doesn't show if I add the code back.
Removing the VStack:
struct WidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
HStack() {
// VStack {
// Spacer()
// VStack(alignment: .leading) {
// Text(DateManager().getDayOfWeekInString(date: entry.date)!)
// .font(Font(UIFont(name: "HoeflerText-Italic", size: 44)!))
// .foregroundColor(.white)
// .multilineTextAlignment(.trailing)
// .opacity(1)
// Text("\(DateManager().getDayAsString(date: entry.date)) \(DateManager().monthAsString(date: entry.date))")
// .font(Font(UIFont(name: "Copperplate", size: 26)!))
// .multilineTextAlignment(.trailing)
// .opacity(1)
// .foregroundColor(.gray)
// }
// }
// .padding(.leading, 20)
// .padding(.bottom, 20)
Spacer()
VStack(alignment: .trailing, spacing: -20) {
Text(DateManager().getHour(date: entry.date))
.font(Font(UIFont(name: "Copperplate-Bold", size: 86)!))
.foregroundColor(.white)
.multilineTextAlignment(.trailing)
.opacity(1)
Text(DateManager().getMinuteWithTwoDigits(date: entry.date))
.font(Font(UIFont(name: "Copperplate-Bold", size: 86)!))
.multilineTextAlignment(.trailing)
.opacity(1)
.foregroundColor(.gray)
}
.padding(.trailing, 15)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Image(uiImage: #imageLiteral(resourceName: "moon")), alignment: .center)
}
}
It does show the UI elements:
https://github.com/beanut/images/blob/main/IMG_002DAA718D1C-1.jpeg?raw=true
Please help me with it and thanks in advance :)!
I stumbled upon this question trying to find a fix for this same issue for myself, and have since found a solution.
I don't believe the VStack was the problem here, but rather force unwrapping one or more of the values for the UI elements inside it, as in this line for example:
Text(DateManager().getDayOfWeekInString(date: entry.date)!)
Force unwrapping a nil value was what was stopping drawing widget's UI for all sizes for me. Try unwrapping the values the safe way or provide a fallback by nil coalescing.
This might be because you call WidgetCenter.shared.reloadAllTimelines in the getTimeline function:
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
...
for offset in 0 ..< 60 * 24 {
WidgetCenter.shared.reloadAllTimelines() // <- this is wrong
let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: midnight)!
entries.append(SimpleEntry(date: entryDate))
}
let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
completion(timeline)
}
The whole point of WidgetCenter.shared.reloadAllTimelines is to force refresh the timeline and effectively call the getTimeline function again.
It's a bad idea to call it from inside getTimeline, especially in the loop.
The code in previews may be working because the getTimeline function is called once only and all repetitive calls are ignored.
I'm using SwiftUI and having some problem passing my string array to a view.
Let's me explain the situation. I'm working on a Gallery app to show some artist's paintings.
I create a TabView to show all the paintings from each artist without any problem but I wanted to make each paintings clickable to see the detail view of the painting, and this where I get stuck.
Every time I click on a paintings it's show me the same paintings...
here is the samples code:
Model & View Model
let artistData: [Artist] = [
Artist(
name: "Piotre",
profilePic: "Piotre",
biography: "Piotre, est un...",
worksImages: [
"GO LOVE YOUR SELF 115.5-89",
"GRAFFITI THERAPIE 146-226",
"HELLO MY NAME IS 130-162",
"KING'S GARDEN 162-130",
"LION'S GARDEN 100-100",
"R'S GARDEN 162 130"
],
workName: [
"GO LOVE YOUR SELF",
"GRAFFITI THERAPIE",
"HELLO MY NAME IS",
"KING'S GARDEN",
"LION'S GARDEN",
"R'S GARDEN"
], workSize: [
"115.5-89",
"146-226",
"130-162",
"162-130",
"100-100",
"162 130"
]),
]
struct Artist: Identifiable {
var id = UUID()
var name: String
var profilePic: String
var biography: String
var worksImages: [String]
var workName: [String]
var workSize: [String]
}
struct ArtistGalleryView: View {
//MARK:- PROPERTIES
var work: Artist
//MARK:- BODY
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.6549019608, green: 0.7137254902, blue: 0.862745098, alpha: 1)).opacity(0.2)
.edgesIgnoringSafeArea(.all)
VStack {
//MARK:- Tableaux
TabView {
ForEach(work.worksImages, id: \.self) { works in
Image(works)
.resizable()
.scaledToFit()
}
.padding()
}
.tabViewStyle(PageTabViewStyle())
}
}
.frame(width: 330, height: 400, alignment: .center)
.cornerRadius(10)
}
}
struct GalleryView: View {
//MARK:- PROPERTIES
var artist: [Artist] = artistData
init() {
UINavigationBar.appearance().titleTextAttributes = [.font: UIFont(name: "WorkSans-Bold", size: 20)!]
}
//MARK:- BODY
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
VStack {
ForEach(artist) { item in
VStack(alignment: .leading) {
ArtistName(artist: item)
NavigationLink(destination: WorksDetailView(work: item)) {
ArtistGalleryView(work: item)
}
}
.buttonStyle(PlainButtonStyle())
}
.padding()
}
}
.navigationBarItems(trailing:Button(action: {
}) {
}
)
.navigationBarTitle(
Text("Gallery"), displayMode: .inline)
}
}
}
And my detail view where I want to see the corresponding image
struct WorksDetailView: View {
#State private var moveUp = false
var work: Artist
var body: some View {
ZStack {
VStack(alignment: .leading) {
Text(work.workName[0])
.modifier(CustomFontModifier(size: 22, name: "WorkSans-Bold"))
.padding(.horizontal)
Text(work.workSize[0])
.modifier(CustomFontModifier(size: 17, name: "WorkSans-Light"))
.foregroundColor(.secondary)
.padding(.horizontal)
VStack {
Image(work.worksImages[0])
.resizable()
.scaledToFit()
}
.padding()
}
.padding(.horizontal)
.padding(.bottom, 150)
Button(action: {
print("Show AR")
}) {
NeumorphicButton(moveUp: $moveUp)
}
.onAppear(perform: {
withAnimation(.easeInOut(duration: 1)) {
moveUp.toggle()
}
})
.offset(y: moveUp ? 225 : 450)
}
}
}
Piece of code that I made for this question that runs in Playground:
import SwiftUI
struct Player {
let name: String
let surname: String
}
struct DetailView2: View {
var player: Player
var body: some View {
Text("\(player.name) \(player.surname)")
}
}
struct PlaygroundView: View {
// MARK: - Propertiers
#State private var selection = 0
private var players = [
Player(name: "Lionel", surname: "Messi"),
Player(name: "Diogo", surname: "Jota"),
]
// MARK: - View
var body: some View {
TabView(selection: $selection) {
NavigationView {
VStack {
Text("Settings").font(.title)
List(0..<players.count, id: \.self) { index in
NavigationLink(destination: DetailView2(player: players[index])) {
Text("\(index)")
}
}
}
.navigationTitle("Players")
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(true)
}
.background(Color.white)
.tabItem {
Image(systemName: "house.fill")
Text("Players")
}
.tag(0)
VStack {
Text("Settings").font(.title)
List(0..<2, id: \.self) { index in
Text("#\(index)")
}
}
.tabItem {
Image(systemName: "book.fill")
Text("Foo")
}
.tag(1)
}
}
}
struct Playground_Previews: PreviewProvider {
static var previews: some View {
PlaygroundView()
}
}
Current Players bar:
Current Settings bar:
How can I fix the code such that "Players" bar will look like Settings bar (it terms of the styling of the title). It seems like I've got the tab item and navigation working already.
It seems you're looking something like below (prepared & tested with Xcode 12.1 / iOS 14.1)
var body: some View {
TabView(selection: $selection) {
VStack {
Text("Settings").font(.title)
NavigationView {
List(0..<players.count, id: \.self) { index in
NavigationLink(destination: DetailView2(player: players[index])) {
Text("\(index)")
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(true)
}
}
.background(Color.white)
.tabItem {
Image(systemName: "house.fill")
Text("Players")
}
.tag(0)
VStack {
Text("Settings").font(.title)
List(0..<2, id: \.self) { index in
Text("#\(index)")
}
}
.tabItem {
Image(systemName: "book.fill")
Text("Foo")
}
.tag(1)
}
}