Widget UI preview in SwiftUI - swiftui

How can I insert the preview of a widget into the app?
Can anyone give me some advice?

I did a naive version of a Widget configuration preview using SwiftUI. I used a RoundedRectangle with a small 3D rotation and applied a shadow:
I contained the code inside a custom View called WidgetView:
struct WidgetView<Content: View>: View {
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 24, style: .continuous)
.fill(Color(.systemBackground))
content
}
.frame(width: 162, height: 162)
.rotation3DEffect(.degrees(10), axis: (x: 1, y: 1, z: 0))
.shadow(color: Color(UIColor.systemGray), radius: 20, x: -5, y: 10)
}
}
The usage is quite simple. Just add the content view you want inside the Widget view:
WidgetView {
HealthActiveEnergyView(entry: ActiveEnergyEntry.mock())
}
.preferredColorScheme(.light)
.previewLayout(layout)
struct HealthActiveEnergyView: View {
var entry: ActiveEnergyEntry
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(NSLocalizedString("Active", comment: ""))
.foregroundColor(entry.configuration.accentColor)
Spacer()
Image(systemName: "flame.fill")
.font(.system(.title))
.foregroundColor(entry.configuration.accentColor)
}
Text(dateFormatter.string(from: entry.date))
.foregroundColor(Color(.systemGray))
.font(.caption)
HStack(alignment: .lastTextBaseline) {
Text("\(entry.totalOfKilocalories)")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(Color(.label))
Text("kcal")
.foregroundColor(Color(.systemGray))
Spacer()
}
Spacer()
Text(NSLocalizedString("AVG 7 DAYS", comment: ""))
.foregroundColor(Color(.systemGray))
.font(.caption)
HStack(alignment: .lastTextBaseline) {
Text("\(entry.avgOfKilocalories)")
.font(.system(.callout, design: .rounded))
.bold()
.foregroundColor(Color(.label))
Text("kcal")
.foregroundColor(Color(.systemGray))
.font(.caption)
Spacer()
}
}
.padding()
}
}

Related

How to give shadow with cornerRadius to a Button inside ScrollView and HStack in SwiftUI

I'm trying to give a shadow to Button using following code.
VStack{
HStack(){
Text("Menu")
.font(.title3)
.fontWeight(.bold)
Spacer()
Image(systemName: "magnifyingglass")
}.padding(.horizontal)
ScrollView(.horizontal){
HStack(){
ForEach(menuOptions, id: \.self){m in
MenuButtons(title:"\(m)")
}
}.frame(height:50)
}
.padding(.horizontal)
.background(.clear)
}.background(.clear)
And my MenuButton struct:
struct MenuButtons: View {
var title: String
var body: some View {
Button(action:{}, label: {
Text(title)
.font(.system(size: 17))
.frame(width:120, height:35)
.cornerRadius(10)
.foregroundColor(Color("secondaryColor"))
.background(Rectangle().fill(.white).shadow(color:.gray,radius:2).cornerRadius(10))
})
}
}
this would be my output:
Expected output:
In above image you can see that shadow is not proper.
How can I achieve the following?
#Dans code works. But its not so much the .tint as the .background of the button, and the order of applying modifiers.
You used a rectangle, then applied shadow, then used cornerRadius. In this order the cornerRadius "cuts off" the shadow applied before.
If you use RoundedRectangle instead (as Dan did) it works fine.
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.white)
.shadow(color:.gray,radius: 2, y: 1)
)
Or you just change the ordering of the modifiers. First cornerRadius, then shadow.
.background(
Rectangle()
.fill(.white)
.cornerRadius(12)
.shadow(color:.gray,radius: 2, y: 1)
)
I believe adding a .buttonStyle(.borderedProminent) and .tint(.clear) may help achieve your desired look. Something like this;
struct ButtonView: View {
let menuOptions = ["Entradas", "Bebidas", "Plato Fuerte", "Postre"]
var body: some View {
VStack{
HStack(){
Text("Menu")
.font(.title3)
.fontWeight(.bold)
Spacer()
Image(systemName: "magnifyingglass")
}.padding(.horizontal)
ScrollView(.horizontal) {
HStack {
ForEach(menuOptions, id: \.self){ m in
MenuButtons(title:"\(m)")
}
}.frame(height:50)
}
.padding(.horizontal)
//.background(.clear)
}//.background(.clear)
}
}
struct MenuButtons: View {
var title: String
var body: some View {
Button(action:{ }, label: {
Text(title)
.font(.system(size: 17))
.frame(width: 120, height: 35)
.cornerRadius(10)
.foregroundColor(.black)
.background(
RoundedRectangle(cornerRadius: 10, style: .circular)
.fill(.white)
.shadow(color: .gray, radius: 2, x: -1, y: 2)
)
})
.buttonStyle(.borderedProminent)
.tint(.clear)
}
}
Result

How to change size of field in TextField?

I need to change the size (actually just the height) of the TextField. Using .padding() increases just the area around the TextField, but not the field itself. I've tried some possible solutions:
Change the font size: This approach works, but I don't want to have a TextField with a too large text in itself.
Using custom modifier doesn't work. I get the message Inheritance from non-protocol type 'TextFieldStyle'. Seems to not working in Swift 5 anymore?
My current solution is:
#State private var searchText: String = ""
var body: some View {
HStack{
Image(systemName: "magnifyingglass")
.font(.system(size: 20, weight: .bold))
ZStack(alignment: .leading){
if searchText.isEmpty { Text("search") }
TextField("", text: $searchText,
onEditingChanged: { focused in
if focused {
// do something...
}
},
onCommit: {
// do something...
})
.keyboardType(.alphabet)
.disableAutocorrection(true)
.frame(height: 50)
}
.textFieldStyle(PlainTextFieldStyle())
}
.frame(height: 30)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.accentColor(.white)
.padding()
.background(
Color("Color-2")
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.3), radius: 5, x: 5, y: 5)
)
.contentShape(Rectangle())
}}
Only the red area is "clickable"
import SwiftUI
struct ContentView: View {
#State var stringOfText: String = ""
var body: some View {
TextField("", text: $stringOfText)
.font(Font.system(size: 50))
.background(Color.yellow)
.foregroundColor(Color.clear)
.cornerRadius(10)
.overlay(Text(stringOfText), alignment: .leading)
.padding()
.shadow(radius: 10)
}
}

Variable Rectangle() dimension based on cell size to draw a timeline

I am trying to build a List that I want to look like a timeline.
Each cell will represent a milestone.
Down the left hand side of the table, I want the cells to be 'connected', by a line (the timeline).
I have tried various things to get it to display as I want but I have settled with basic geometric shapes , i.e Circle() and Rectangle().
This is sample code to highlight the problem:
import SwiftUI
struct ContentView: View {
var body: some View {
let roles: [String] = ["CEO", "CFO", "Managing Director and Chairman of the supervisory board", "Systems Analyst", "Supply Chain Expert"]
NavigationView{
VStack{
List {
ForEach(0..<5) { toto in
NavigationLink(
destination: dummyView()
) {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .center, spacing: 0){
Rectangle()
.frame(width: 1, height: 30, alignment: .center)
Circle()
.frame(width: 10, height: 10)
Rectangle()
.frame(width: 1, height: 20, alignment: .center)
Circle()
.frame(width: 30, height: 30)
.overlay(
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.system(size: 30, weight: .light , design: .rounded))
.frame(width: 30, height: 30)
)
//THIS IS THE RECTANGLE OBJECT FOR WHICH I WANT THE HEIGHT TO BE VARIABLE
Rectangle()
.frame(width: 1, height: 40, alignment: .center)
.foregroundColor(.green)
}
.frame(width: 32, height: 80, alignment: .center)
.foregroundColor(.green)
VStack(alignment: .leading, spacing: 0, content: {
Text("Dummy operation text that will be in the top of the cell")
.font(.subheadline)
.multilineTextAlignment(.leading)
.lineLimit(1)
Label {
Text("March 6, 2021")
.font(.caption2)
} icon: {
Image(systemName: "calendar.badge.clock")
}
HStack{
HStack{
Image(systemName: "flag.fill")
Text("In Progress")
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.blue)
.background(Color.white)
.cornerRadius(5, antialiased: true)
HStack{
Image(systemName: "person.fill")
Text(roles[toto])
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.green)
.background(Color.white)
.cornerRadius(5, antialiased: true)
HStack{
Image(systemName: "deskclock")
Text("in 2 Months")
.font(.system(size: 12))
}
.padding(.horizontal, 4)
.padding(.vertical, 3)
.foregroundColor(.red
)
.background(
Color.white
)
.cornerRadius(5, antialiased: true)
}
})
}.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct dummyView: View {
var body: some View {
Text("Hello, World!")
}
}
struct dummyView_Previews: PreviewProvider {
static var previews: some View {
dummyView()
}
}
but as you can see in the enclosed picture, there are unwanted gaps
So other content in the cell is making the height of the entire cell 'unpredictable' and break the line.
Is there a way to determine the height of the cell and extend the dimensions of the Rectangle, so that it extends to the full height of the cell?
Is there a better approach you recommend for trying to build such a timeline ?
PS: I have tried playing around with .frame and .infinity but that does work.
Many thanks.
Why not just draw the line based on the size of the row. See Creating custom paths with SwiftUI. Remember, everything is a view.
First, you need to decompose what you are doing into subviews. You have too many moving parts in one view to get it correct. Also, I would avoid setting specific padding amounts as that will mess you up when you change devices. You want a simple, smart view that is generic enough to handle different devices.
I would have a row view that has a geometry reader so it knows its own height. You could then draw the line so that it spanned the full height of the row, regardless of the height. Something along the lines of this:
struct ListRow: View {
var body: some View {
GeometryReader { geometry in
ZStack {
HStack {
Spacer()
Text("Hello, World!")
Spacer()
}
VerticalLine(geometry: geometry)
}
}
}
}
and
struct VerticalLine: View {
let geometry: GeometryProxy
var body: some View {
Path { path in
path.move(to: CGPoint(x: 20, y: -30))
path.addLine(to: CGPoint(x: 20, y: geometry.size.height+30))
}
.stroke(Color.green, lineWidth: 4)
}
}

SwiftUI Aligning two element in different view containers

I am having difficultly horizontally aligning two elements inside different container views. I want to align the two scores (in red) horizontally. I tried using a custom alignment guide (and custom CoordinateSpace), and although this did align the two scores, it also caused the corresponding stacks two change. What am I missing? Surely there must be an easy way to do this.
struct ContentViewExample: View {
var body: some View {
VStack(alignment: .leading) {
HStack {
Label("Team 1", systemImage: "seal")
.font(.title3)
Spacer()
Text("65")
.background(Color.red)
Text("Final")
.font(.caption)
.padding(.leading)
}
HStack {
Label("Team No 2", systemImage: "seal")
.font(.title3)
Spacer()
Text("70")
.background(Color.red)
}
}.padding(.horizontal)
}
}
My solution would be to use a (Lazy?)VGrid:
struct Result : Identifiable, Hashable {
var id = UUID()
var name: String
var label: String
var score: Int
var final: Bool
}
var finalScore = [Result(name: "Team 1", label: "seal.fill", score: 167, final: true),
Result(name: "Team No 2", label: "seal", score: 65, final: false)]
struct ContentView: View {
private var columns: [GridItem] = [
GridItem(alignment: .leading),
GridItem(alignment: .trailing),
GridItem(alignment: .leading)
]
var body: some View {
LazyVGrid(
columns: columns,
alignment: .center,
spacing: 16,
pinnedViews: [.sectionHeaders, .sectionFooters]
) {
Section(header: Text("Results").font(.title)) {
ForEach(finalScore) { thisScore in
Label(thisScore.name, systemImage: thisScore.label)
.background(Color(UIColor.secondarySystemBackground))
Text(String(thisScore.score))
.background(Color(UIColor.secondarySystemBackground))
Text(thisScore.final == true ? "Final" : "")
.background(Color(UIColor.secondarySystemBackground))
}
}
}
.border(Color(.blue))
}
}
gotta fiddle with the colors though, I just put some backgrounds and a border to see which things are where... And maybe optimize the column's widths if needed.
To read up on Grids I suggest this: https://swiftwithmajid.com/2020/07/08/mastering-grids-in-swiftui/
And btw: custom alignment won't work because the entire HStack would be moved left and right, you'd have to adjust the width of the "columns" there, that would be extra hassle.
I decided to write a medium.com article to answer this question and did just that. Here is the solution looks like and here is the code too. Here is the article and well the solution.
https://marklucking.medium.com/a-real-world-alignment-challenges-in-swiftui-2-0-ff440dceae5a
In short I setup the four labels with the correct alignment and then aligned the four containers with each other.
In this code I included a slider so that you can better understand how it works.
import SwiftUI
struct ContentView: View {
private var newAlignment1H: HorizontalAlignment = .leading
private var newAlignment1V: VerticalAlignment = .top
private var newAlignment2H: HorizontalAlignment = .trailing
private var newAlignment2V: VerticalAlignment = .top
#State private var zeroX: CGFloat = 160
var body: some View {
VStack {
ZStack(alignment: .theAlignment) {
HStack {
Label {
Text("Team 1")
.font(.system(size: 16, weight: .semibold, design: .rounded))
} icon: {
Image(systemName:"seal")
.resizable()
.scaledToFit()
.frame(width: 30)
}.labelStyle(HorizontalLabelStyle())
}
.border(Color.blue)
.alignmentGuide(.theHorizontalAlignment, computeValue: {d in zeroX})
// .alignmentGuide(.theHorizontalAlignment, computeValue: {d in d[self.newAlignment2H]})
// .alignmentGuide(.theVerticalAlignment, computeValue: {d in d[self.newAlignment1V]})
HStack {
Text("65")
.font(.system(size: 16, weight: .semibold, design: .rounded))
.background(Color.red.opacity(0.2))
.frame(width: 128, height: 32, alignment: .trailing)
Text("Final")
.font(.system(size: 16, weight: .semibold, design: .rounded))
.background(Color.red.opacity(0.2))
.frame(width: 48, height: 32, alignment: .leading)
}
.border(Color.green)
}
ZStack(alignment: .theAlignment) {
HStack {
Label {
Text("Team No 2")
.font(.system(size: 16, weight: .semibold, design: .rounded))
} icon: {
Image(systemName:"seal")
.resizable()
.scaledToFit()
.frame(width: 30)
}.labelStyle(HorizontalLabelStyle())
}
// .alignmentGuide(.theHorizontalAlignment, computeValue: {d in d[self.newAlignment2H]})
// .alignmentGuide(.theVerticalAlignment, computeValue: {d in d[self.newAlignment1V]})
.alignmentGuide(.theHorizontalAlignment, computeValue: {d in zeroX})
.border(Color.pink)
HStack {
Text("70")
.font(.system(size: 16, weight: .semibold, design: .rounded))
.background(Color.red.opacity(0.2))
.frame(width: 128, height: 32, alignment: .trailing)
Text("")
.background(Color.red.opacity(0.2))
.frame(width: 48, height: 32, alignment: .leading)
}
.border(Color.orange)
}
VStack {
Slider(value: $zeroX, in: 0...200, step: 10)
Text("\(zeroX)")
}
}
}
}
struct VerticalLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
VStack(alignment: .center, spacing: 8) {
configuration.icon
configuration.title
}
}
}
struct HorizontalLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .center, spacing: 8) {
configuration.icon
configuration.title
}
}
}
extension VerticalAlignment {
private enum TheVerticalAlignment : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[VerticalAlignment.top]
}
}
static let theVerticalAlignment = VerticalAlignment(TheVerticalAlignment.self)
}
extension HorizontalAlignment {
private enum TheHorizontalAlignment : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[HorizontalAlignment.leading]
}
}
static let theHorizontalAlignment = HorizontalAlignment(TheHorizontalAlignment.self)
}
extension Alignment {
static let theAlignment = Alignment(horizontal: .theHorizontalAlignment, vertical: .theVerticalAlignment)
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is a possible approach (a-la column-oriented)
HStack {
// image column
VStack {
Image(systemName: "seal")
Image(systemName: "seal")
}
.font(.title3)
// text column
VStack(alignment: .leading) {
Text("Team 1")
Text("Team No 2")
}
.font(.title3)
Spacer()
// score column
VStack {
Text("65")
Text("70")
}
.background(Color.red)
// note column
VStack {
Text("Final")
Text("")
}
.font(.caption)
.padding(.leading)
}
.frame(maxWidth: .infinity)
.padding(.horizontal)

How to navigate to different screens/views by tapping button which is in ForEach

I have created a dashboard with 9 buttons laid out on a lazyVGrid and a ForEach loop to display the various buttons.
(Click to expand)
Now, I want to navigate to various new screens based on the button pressed. Someone, please help me achieve this.
LazyVGrid(columns: row, spacing: 25) {
ForEach(Dashboard_Data) { data in
Button(action: {
// action
}) {
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
VStack(alignment: .leading, spacing: 5) {
Spacer(minLength: 0)
Text(data.data)
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
HStack{
Spacer(minLength: 0)
Text(data.suggest)
.font(.system(size: 17, weight: .regular))
.lineLimit(2)
.foregroundColor(.white)
}
}.padding()
.background(Color(data.image))
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.5), radius: 5, x: 0, y: 5)
Image(data.imageIcon)
.renderingMode(.template)
.foregroundColor(.white)
.font(.system(size: 25, weight: .regular))
.padding(10)
.background(Color.white.opacity(0.15))
.clipShape(Circle())
}
}
}
}
var body: some View{
LazyVGrid(columns: row, spacing: 25) {
ForEach(Dashboard_Data) { data in
NavigationLink(destination: getTheDestination(data)) {
//label/design of your button
}
}
}
}
private func getTheDestination(data:<put ur dataType of Dashboard_Data> ) -> some View {
// add some input parameter in this func to get data/informattion from your Dashboard_Data
// according to ur data return the destination view
if data.data == "your value" {
return Text( _ data.data)
}
return Text("Destination")
}