I’m new at SwiftUI and I’ve a doubt. I wonder if anyone can help me.
I’ve a screen with two exact buttons (except for the text that they display and the view which they lead to). When they are tapped, each button leads the user to a new given view.
The code is this:
HStack {
NavigationLink(destination: PlayView()) {
ButtonTextView(buttonText: "Play")
}
.frame(width: 120, height: 40, alignment: .center)
.background(Color(red: 242/255, green: 242/255, blue: 242/255, opacity: 1))
.cornerRadius(10.0)
Spacer()
NavigationLink(destination: RankingView()) {
ButtonTextView(buttonText: "Ranking")
}
.frame(width: 120, height: 40, alignment: .center)
.background(Color(red: 242/255, green: 242/255, blue: 242/255, opacity: 1))
.cornerRadius(10.0)
}
And I want to be able to do something like this:
HStack {
ButtonView(buttonIdentifier: "Play")
Spacer()
ButtonView(buttonIdentifier: "Ranking")
}
Where ButtonView is defined like so:
NavigationLink(destination: PlayView()) {
ButtonTextView(buttonText: "Play")
}
.frame(width: 120, height: 40, alignment: .center)
.background(Color(red: 242/255, green: 242/255, blue: 242/255, opacity: 1))
.cornerRadius(10.0)
So, basically, I don’t repeat code and have a much more readable file.
The problem comes when I’ve to define the destination of the NavigationLink. There is some way I can set the destination to be a variable and pass it when I call ButtonView()? So that if the buttonIdentifier is "Play", destination is PlayView() (like above) and if is "Ranking", then is RankingView().
I hope I’ve been able to explain my problem clearly (i’m not a native speaker…)
Any help is appreciated. Thanks a lot :)!
struct ButtonView<Destination>: View where Destination : View {
let buttonText: String
let destination: Destination
init(buttonText: String, #ViewBuilder destination: #escaping () -> Destination) {
self.buttonText = buttonText
self.destination = destination()
}
var body: some View {
NavigationLink(destination: destination) {
Text(buttonText)
}
.frame(width: 120, height: 40, alignment: .center)
.background(Color(red: 242/255, green: 242/255, blue: 242/255, opacity: 1))
.cornerRadius(10.0)
}
}
Use like:
ButtonView(buttonText: "Play") {
PlayView()
}
Related
I just upgraded my phone from 15.x (I think 15.2) to 15.3, and my SwiftUI layout is broken, seems to be a List border issue.
Here is the code, followed by a screen shot of normal behavior (13.x to 15.x) and then what I see in 15.3
struct SessionView: View {
var title: String
var panel: Int
var index: Int
var range: [Int] = [0,2,3,4]
#EnvironmentObject var state: MainViewModel
#EnvironmentObject var content: ContentViewModel
#ViewBuilder
var body: some View {
VStack() {
// -- Header
HStack() {
Text(" ")
Image(self.state.panelIcon(panel: panel)).resizable().frame(width: 12.0, height: 12.0)
Text(title)
Spacer()
}.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
.background(Color(red: 0.9, green: 0.9, blue: 0.9))
.onTapGesture {
showDetailDialog()
}
// -- Rows
List {
ForEach(0..<5) { i in
if i != 1 { // Skip IP address
HStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: -4, content: {
Text("\(state.keyValues[i+index]!.key)").frame(minWidth: 120, maxHeight: 20, alignment: .leading)
.font(Font.system(size: 15, design: .default)).padding(0)
Text("\(state.keyValues[i+index]!.value)").frame(maxHeight: 20, alignment: .leading)
.font(Font.system(size: 15, design: .default)).padding(0)
}).frame(height: 10)
}
}
}.environment(\.defaultMinListRowHeight, 10)
.frame(height: 4*20+20)
.listStyle(DefaultListStyle()).environment(\.defaultMinListRowHeight, 8).onAppear {
UITableView.appearance().isScrollEnabled = false
}.layoutPriority(1)
}.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(red: 0.8, green: 0.8, blue: 0.8), lineWidth: 1.25)
).background(Color.white)
}
.
.
.
}
I had this problem too. However, it is not a border issue. When you updated your iPhone, the default list style for SwiftUI changed. The old default style is now called PlainListStyle(). Use that instead to get the old look back.
List {
}.listStyle(PlainListStyle())
i have this card thing in the picture to build, here're the code:
struct ArticleCard: View {
var article: Article
var body: some View {
ZStack {
Color.white
.border(Color(red: 0, green: 0, blue: 0, opacity: 0.2))
.shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.2), radius: 0.0, x: 5, y: 5)
VStack{
Text(article.title)
.padding(.top, 5)
.padding(.leading)
.padding(.trailing)
.font(.title2)
Image(article.coverUrl).resizable()
.frame(height: 200)
.padding(.leading)
.padding(.trailing)
Text(article.title)
.padding(.leading)
.padding(.trailing)
.padding(.bottom)
}
}
}
}
in the ZStack i put a Color.white there as a background of the card, and give this color view a shadow, but the color seems to be transparent, therefor i got unwanted lines on the top and the left inside the borders, how do i get rid of them?
You are using border and shadow in wrong place, try this:
PS: there is no reason of using Color(red: 0, green: 0, blue: 0, opacity: 0.2) you can use this: Color.black.opacity(0.2)
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.white
VStack {
Text("article.title")
.padding()
Image(systemName: "star")
.resizable()
.scaledToFit()
Text("article.title")
.padding()
}
}
.frame(width: 300, height: 300, alignment: .center)
.border(Color(red: 0, green: 0, blue: 0, opacity: 0.2))
.compositingGroup()
.shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.2), radius: 0.0, x: 5, y: 5)
}
}
I refactored your code for better and less code as possible you can use Image instead of Text in your App.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("your custom Text")
.padding()
Text("🥩")
.font(Font.system(size: 150))
Text("your custom Text")
.padding()
}
.frame(width: 300, height: 300, alignment: .center)
.background(Color.white)
.border(Color.black.opacity(0.2))
.compositingGroup()
.shadow(color: Color.black.opacity(0.2), radius: 0.0, x: 5, y: 5)
}
}
I am trying to create a medium Widget like in YouTube Music, but I am don't understand how to create an interaction with the particular item in a Widget. How my app should understand when user press on first or second item and then how I am must handle this action inside app. My app use Swift not SwiftUI, only for a Widget I use SwiftUI. In past I didn't have experience with a SwityUI.
My code for Widget:
struct WidgetTestEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
HStack(spacing: 100){
Text("Favourite").foregroundColor(.white).font(.system(size: 16, weight: .bold, design: .default))
Image("Label").resizable().frame(width: 80, height: 15, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
}.frame(maxWidth: .infinity, maxHeight: 50, alignment: .center).background(Color.black).offset(y: -9)
HStack {
Spacer()
Button(action: {}) {
Image("").resizable().frame(width: 70, height: 70)
.cornerRadius(10)
.background(Color(red: 0.218, green: 0.215, blue: 0.25))
}.cornerRadius(10).onTapGesture {
let a = ViewController()
a.data.text = "Tap"
}
Button(action: {}) {
Image("").resizable().frame(width: 70, height: 70)
.cornerRadius(10)
.background(Color(red: 0.218, green: 0.215, blue: 0.25))
}.cornerRadius(10).onTapGesture {
let a = ViewController()
a.data.text = "Tap"
}
Button(action: {}) {
Image("").resizable().frame(width: 70, height: 70)
.cornerRadius(10)
.background(Color(red: 0.218, green: 0.215, blue: 0.25))
}.cornerRadius(10).onTapGesture {
let a = ViewController()
a.data.text = "Tap"
}
Button(action: {}) {
Image("").resizable().frame(width: 70, height: 70)
.cornerRadius(10)
.background(Color(red: 0.218, green: 0.215, blue: 0.25))
}.cornerRadius(10).onTapGesture {
let a = ViewController()
a.data.text = "Tap"
}
Spacer().frame(width: 10, height: 10, alignment: .center)
}.background(Color(red: 0.118, green: 0.118, blue: 0.15)).offset(y: -9)
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center).background(Color(red: 0.118, green: 0.118, blue: 0.15))
}
}
You can do this using Link in SwiftUI.
Link(destination: url, label: {
// add your UI components in this block on which you want to perform click action.
}
url: Provide a unique string to identify widget action in the main app.
Now In the main app, use below method in AppDelegate.swift file:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
}
In this method, you can identify the URL that comes from the WidgetKit action.
It seems there is a potential bug in SwiftUI. I am trying to put a rectangle with opacity 0.5 on top of an image.
When I try to fix the transparent rectangle on top, from 100px width, it goes down instead of sticking to the top.
Here is the code:
ZStack {
VStack {
Image("movistar")
.resizable(capInsets: EdgeInsets(), resizingMode: .stretch)
.scaledToFit()
.cornerRadius(8)
.padding(15)
.frame(minWidth: Global.SCREEN_WIDTH)
}
VStack {
HStack {
Rectangle()
.fill(Color(red: 0, green: 0, blue: 0, opacity: 0.5))
.frame(width: 110, height: Global.SCREEN_WIDTH / 4)
}
Spacer()
}
.scaledToFit()
.cornerRadius(8)
.padding(15)
.frame(width: Global.SCREEN_WIDTH, height: Global.SCREEN_WIDTH)
There is no bug here. If you add a .background to all of your layers, you will see that because of the way you set up the view (ie. Spacer, scaledToFit, etc.) the actual frames of the views are not necessarily the edges of the image. You also have not set the alignment of any of the Stacks or Frames.
There are many ways to do what you are trying to do, but I believe this is the simplest:
var body: some View {
Image("movistar")
.resizable(capInsets: EdgeInsets(), resizingMode: .stretch)
.scaledToFit()
.cornerRadius(8)
.frame(minWidth: UIScreen.main.bounds.width)
.overlay(
Rectangle()
.fill(Color(red: 0, green: 0, blue: 0, opacity: 0.5))
.frame(width: 110, height: UIScreen.main.bounds.width / 4)
, alignment: .top
)
}
Finally got into a solution: .scaleToFit() was messing with the VStack(). After deleting, it worked perfectly. I also got rid of the HStack().
I cannot figure out what compositingGroup() is. At first, I thought it is something like Merging layers in Photoshop. But it was not. Because .shadow() effects to the overlay and background views respectively even if I use .compositingGroup().
So far, I've found 2 differences when I use .compositingGroup()
Text doesn't have shadows.
The shadow size of the overlay view is slightly smaller than the above one.
What is the purpose of compositingGroup?
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Text("Without\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.shadow(color: .blue, radius: 5)
Text("With\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.compositingGroup() // <--- I added .compositingGroup() here.
.shadow(color: .blue, radius: 5)
}
}
}
This modifier makes the following modifiers be applied to the view as a whole and not to each particular subview separately
Here's an example to better illustrate this:
struct ContentView: View {
let circles: some View = ZStack {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
.offset(y: -25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
.offset(x: -25, y: 25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.green)
.offset(x: 25, y: 25)
}
var body: some View {
VStack(spacing: 100) {
circles
circles
.opacity(0.5)
circles
.compositingGroup()
.opacity(0.5)
}
}
}
So in your case the shadow is applied to the whole view rather than separately to the Text and overlaying RoundedRectangle
Use it when wanting to apply effects like opacity or shadow to a group of views and not each contained element by itself.
It seems like that .shadow() modifier will add both inner and outer shadow. It means that if the view is not "solid", for example, it has a "hole", .shadow() will add shadow like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.shadow(color: .blue, radius: 5)
Click to see the image
So, if you do not want the inner shadow, you need to make your view be "solid", like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.shadow(color: .blue, radius: 5)
Click to see the image
However, something goes wrong again, the inner shadow doesn't disappear.
That's because I forgot to apply the .compositingGroup() modifier.
As #ramzesenok mentioned, .compositingGroup() makes the following modifiers be applied to the view as a whole and not to each particular subview separately.
So, change the code a little bit:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.compositingGroup()
.shadow(color: .blue, radius: 5)
Click to see the image
There is only outer shadow now.