iOS 15.3 has broken SwiftUI layout - swiftui

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())

Related

SwiftUI overlay deprecated alternatives

I'm trying to follow this calculator tutorial but am running into several issues. One of the issues is the use of the .overlay method. I'm assuming it doesn't work because it is deprecated, but I can't figure out how to get the recommeded way or get anything else to work to solve it, so am reaching out for options.
Xcode 12.4
Target: iOS 14.4
Here is the code in that section:
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(foregroundColor)
/* //Commented out to compile
.overlay( {
if configuration.isPressed {
Color(white: 1.0, opacity: 0.2)
}
)
}
*/
//.clipShape(Capsule()) //this makes circle buttons
} //func
} //struct
I've tried commenting out that section which is the only way to have it compile, but then the button press action of showing a different color does not work.
Overlay is not deprecated. Where did you read that. I think your problem is that you try to use the overlay function in a button style, which is not possible. You can only use it in a view. The error you wrote behind it, also states that.
I'm also not sure what you want to achieve, because doesn't it work correct if you just not use the overlay?
I get this button without the overlay. Is that what you need?
With the button style:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button("1") {
print("being pressed")
}
.buttonStyle(
CalculatorButtonStyle(
size: 40,
backgroundColor: .cyan,
foregroundColor: .black
)
)
}
.padding()
}
}
I also added the changed button style.
import SwiftUI
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(configuration.isPressed ? Color(white: 1.0, opacity: 0.2) : foregroundColor)
.clipShape(Capsule()) //this makes circle buttons
}
}
An overlay is not needed. Take advantage of the isPressed value of the configuration to change the color
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(configuration.isPressed
? Color(white: 1.0, opacity: 0.5)
: foregroundColor)
.clipShape(Capsule())
} //func
} //struct
You have two stray parentheses:
// Remove this
// ↓
.overlay( {
if configuration.isPressed {
Color(white: 1.0, opacity: 0.2)
}
) // ← Remove this
}
The stray left parenthesis (the one immediately after .overlay) prevents Swift from recognizing the trailing closure syntax and makes Swift think you are trying to use the deprecated version of overlay.
The stray right parenthesis is improperly nested relative to the right-brace on the following line.
If you copy and paste the code from the tutorial, or if you remove those two parentheses, the code compiles:
struct CalculatorButtonStyle: ButtonStyle {
var size: CGFloat
var backgroundColor: Color
var foregroundColor: Color
var isWide: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.system(size: 32, weight: .medium))
.frame(width: size, height: size)
.frame(maxWidth: isWide ? .infinity : size, alignment: .leading)
.background(backgroundColor)
.foregroundColor(foregroundColor)
.overlay {
if configuration.isPressed {
Color(white: 1.0, opacity: 0.2)
}
}
.clipShape(Capsule())
}
}

SwiftUI: Adding a Shadow to View in List/Grid is Lagging UI

When adding a shadow to my view in a Grid, the scrolling experience is bad. It feels like the Frame Rate is dropping. I came across this post, option 1 made my whole background for my view the same color as my shadow. I Don't really know how to implement UIViewRepresentable in option 2.
So how would I be able to use UIViewRepresentable, or is there a better way to do this.
MRE CODE
struct ContentView: View {
#State var gridSpacing: CGFloat = 8
let columns: [GridItem] = [GridItem(.flexible(),spacing: 8), GridItem(.flexible(),spacing: 8),GridItem(.flexible(),spacing: 8)]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: gridSpacing) {
ForEach(0..<200) { x in
VStack(spacing: 8) {
Image("sumo-deadlift") //<----- Replace Image
.resizable()
.scaledToFit()
.background(.yellow)
.clipShape(Circle())
.padding(.horizontal)
.shadow(color: .blue, radius: 2, x: 1, y: 1) //<----- Comment/Uncomment
Text("Sumo Deadlift")
.font(.footnote.weight(.semibold))
.multilineTextAlignment(.center)
.lineLimit(2)
.frame(maxWidth: .infinity)
Text("Legs")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.all)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
.mask(RoundedRectangle(cornerRadius: 20))
.shadow(color: .red, radius: 2, x: 1, y: 1) //<----- Comment/Uncomment
}
}
.padding(.all, 8)
}
.background(.ultraThinMaterial)
}
}

SwiftUI NavigationItem dynamic data sending incorrect parameter

I have what I thought was a simple task, but it appears otherwise. As part of my code, I am simply displaying some circles and when the user clicks on one, it will navigate to a new view depending on which circle was pressed.
If I hard code the views in the NavigationLink, it works fine, but I want to use a dynamic array. In the following code, it doesn't matter which circle is pressed, it will always display the id of the last item in the array; ie it passes the last defined dot.destinationId to the Game view
struct Dot: Identifiable {
let id = UUID()
let x: CGFloat
let y: CGFloat
let color: Color
let radius: CGFloat
let destinationId: Int
}
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
.frame(width: dot.radius, height: dot.radius)
.position(x:dot.x, y: dot.y )
}
}
}
.frame(width: .infinity, height: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
struct Game: View {
var id: Int
var body: some View {
Text("\(id)")
}
}
I tried to send the actual view as i want to show different views and used AnyView, but the same occurred It was always causing the last defined view to be navigated to.
I'm really not sure what I a doing wrong, any pointers would be greatly appreciated
While it might seem that you're clicking on different dots, the reality is that you have a ZStack with overlapping views and you're always clicking on the top-most view. You can see when you tap that the dot with ID 6 is always the one actually getting pressed (notice its opacity changes when you click).
To fix this, move your frame and position modifiers outside of the NavigationLink so that they affect the link and the circle contained in it, instead of just the inner view.
Also, I modified your last frame since there were warnings about an invalid frame size (note the different between width/maxWidth and height/maxHeight when specifying .infinite).
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack {
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
}
.frame(width: dot.radius, height: dot.radius) //<-- Here
.position(x:dot.x, y: dot.y ) //<-- Here
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
}

#State variables across multiple views and inside data arrays - Observables, Environment Published?

I've got myself in a bit of a pickle. I'm learning swift via a variety of online tutorials and at the same time coding a side project as further self learning. The side project will be a VERY basic football manager style app which I figure will give me a bit of UI, data handling, and algorithm refactoring practice. It is this side project where I have hit a blocker. I understand the principle of state variables to maintain a permanent link between an app state and the UI and I also understand the general principle of using observable objects / environment and and #Published to allow those variables to be available in multiple "views" however the implementation / syntax is eluding me.
The (lengthy) code below basically sets up a home screen on the UI where two teams abilities are compared. This is done via ability bars and a radar chart. Both the ability bar and the radar chart work correctly but I can't seem to link them to the same variable. The ability bar uses a series of #State variables called in the main content.view and then passed via $bindings from a child view to its parent but the radar plot is populated by local variables within a data array. I can't work out how to put the state variables into the array, either as is, or by using observable or environmental variables. Basically I want to declare the 6 variables in one location and then use them inside the array and also inside the various views.
I'd be really appreciative if someone could offer some suggestions or indeed to point me in a different direction if I am completely on a tangent with this.
By way of a quick summary...
First I have an ability bar view which creates something similar to a progress bar...
//Ability Bar View
struct abilityBar: View {
#Binding var value: Double
var color1: Color
var color2: Color
var title: String
var barHeight: Float
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: -3) {
HStack {
Text("\(title)")
.modifier(lightFont())
Spacer()
Text("\(self.value, specifier: "%.1f")")
.modifier(heavyFont())
}
Spacer()
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 4)
.frame(width: geometry.size.width, height: CGFloat(barHeight))
.foregroundColor(.white)
.opacity(0.075)
RoundedRectangle(cornerRadius: 4)
.fill(LinearGradient(
gradient: Gradient(colors: [color1, color2]),
startPoint: .leading,
endPoint: .trailing))
.frame(width: ((CGFloat(self.value)/10) * geometry.size.width), height: CGFloat(barHeight))
}
}
}
}
}
Second I have a team widget view that nests these ability bars and is passed variables by way of $Bindings
//Team Widget View
struct teamWidget: View {
#Binding var defence: Double
#Binding var midfield: Double
#Binding var attack: Double
var color1: Color
var color2: Color
var location: String
var teamName: String
var position: String
var body: some View {
ZStack {
Rectangle()
.frame(height: 185.0)
.foregroundColor(Color(red: 0.23921568627450981, green: 0.24313725490196078, blue: 0.3215686274509804))
.shadow(color: .black, radius: 4, x: 4, y: 4)
HStack {
Rectangle()
.fill(LinearGradient(
gradient: Gradient(colors: [color1, color2]),
startPoint: .top,
endPoint: .bottom))
.frame(height: 185.0)
.frame(width: 8)
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 0.0) {
Text("\(location)")
.modifier(lightFont())
Text("\(teamName)")
.modifier(heavyFont())
Text("League Position")
.modifier(lightFont())
Text("\(position)")
.modifier(heavyFont())
}
VStack(spacing: 4.0) {
abilityBar(value: $defence, color1: color1, color2: color2, title: "Defence", barHeight: 9.0)
.frame(height: 28)
abilityBar(value: $midfield, color1: color1, color2: color2, title: "Midfield", barHeight: 9.0)
.frame(height: 28)
abilityBar(value: $attack, color1: color1, color2: color2, title: "Attack", barHeight: 9.0)
.frame(height: 28)
}
}
Spacer()
}
}
}
}
These are populated in the main content view via #State variables. Also in the main content view is a data array that is used to draw a radar chart (main code in a separate .swift file. What I cant work out how to do is call the same #State variables in this array. Or whether I need to define the state variables differently (observable / environment etc). For reference in this large body of code I ahve commented 3 lines (1) - where the state variables are declared (2) where the data array for the radar chart is declared and where I currently cant get state variables or equivalent to work and (3) where the ability bars are declared (work with state variables)
Apologies for the long main / lots of code. I couldn't think of a way to cut this down further.
enum RayCase:String, CaseIterable {
case defence = "Def"
case midfield = "Mid"
case attack = "Att"
}
struct DataPoint:Identifiable {
var id = UUID()
var entrys:[RayEntry]
var color:Color
init (defence:Double, midfield:Double, attack:Double, color:Color) {
self.entrys = [
RayEntry(rayCase: .defence, value: defence),
RayEntry(rayCase: .attack, value: attack),
RayEntry(rayCase: .midfield, value: midfield),
]
self.color = color
}
}
struct ContentView: View {
// 1 THIS IS WHERE STATE VARIABLES ARE DECLARED
#State var team1Defence: Double = Double.random(in: 1.0...9.9)
#State var team1Midfield: Double = Double.random(in: 1.0...9.9)
#State var team1Attack: Double = Double.random(in: 1.0...9.9)
#State var team2Defence: Double = Double.random(in: 1.0...9.9)
#State var team2Midfield: Double = Double.random(in: 1.0...9.9)
#State var team2Attack: Double = Double.random(in: 1.0...9.9)
#State var playMatchIsVisible: Bool = false
let mainRed = Color(red: 245/255, green: 39/255, blue: 85/255)
let highlightRed = Color(red: 245/255, green: 85/255, blue: 141/255)
let mainBlue = Color(red: 62/255, green: 219/255, blue: 208/255)
let highlightBlue = Color(red: 119/255, green: 237/255, blue: 194/255)
let dimensions = [
Ray(maxVal: 10, rayCase: .attack),
Ray(maxVal: 10, rayCase: .midfield),
Ray(maxVal: 10, rayCase: .defence),
]
// 2 THIS IS THE DATA ARRAY WHERE I CANT GET STATE VARIABLES TO WORK, THE SAME 6 DECLARED VARIABLES NEED TO POPULATE THIS ARRAY
let data = [
DataPoint(defence: 5, midfield: 8, attack: 7, color: .red),
DataPoint(defence: 5, midfield: 2, attack: 9, color: .blue),
]
var body: some View {
//Background Z-Stack Container
ZStack {
LinearGradient(gradient: Gradient(colors: [(Color(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.32941176470588235)), (Color(red: 0.058823529411764705, green: 0.06666666666666667, blue: 0.12941176470588237))]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
//Main VStack
VStack {
//Row 1 Main HStack
HStack {
//Date / Fixture ZStack
ZStack {
Rectangle()
.frame(height: 70.0)
.foregroundColor(Color(red: 0.23921568627450981, green: 0.24313725490196078, blue: 0.3215686274509804))
.shadow(color: /*#START_MENU_TOKEN#*/.black/*#END_MENU_TOKEN#*/, radius: 4, x: 4, y: 4)
//Date Fixture HStack to ensure left alignment
HStack {
// Date / Fixture VStack to put text on top of each other (6)
VStack(alignment: .leading, spacing: 4.0) {
Text("Date")
.modifier(heavyFont())
Text("Saturday 23rd October")
.modifier(lightFont())
Text("League 2 Fixture")
.modifier(heavyFont())
}
Spacer() //Forces text to LHS of widget
}
.padding(.leading, 7)
}
.padding(.leading, 15)
.padding(.trailing, 6)
// Play Match Stack (7)
ZStack {
Rectangle()
.frame(height: 70.0)
.foregroundColor(Color(red: 0.23921568627450981, green: 0.24313725490196078, blue: 0.3215686274509804))
.shadow(color: /*#START_MENU_TOKEN#*/.black/*#END_MENU_TOKEN#*/, radius: 4, x: 4, y: 4)
// Play Match HStack to ensure correct left alignment (8)
HStack {
Button(action: {
self.playMatchIsVisible = true
}) {
Image("playFull")
Text("Play Match")
.modifier(heavyFont())
}
.alert(isPresented: $playMatchIsVisible, content: {
return Alert(title: Text("Are you sure you want to kick off?"), primaryButton: .default(Text("Yes, Let's play!")), secondaryButton: .default(Text("No, not yet.")))
})
}
}
.padding(.leading, 6)
.padding(.trailing, 15)
}
.padding(.top, 15)
//Row 2 HStack
HStack {
// 3 THIS IS WHERE STATE VARIABLES ARE CALLED TO POPULATE THE ABILITY BARS AND WORK IN THIS CONTEXT AND WHEEN PASSED AS BINDINGS INTERNALLY
teamWidget(defence: $team1Defence, midfield: $team1Midfield, attack: $team1Attack, color1: highlightRed, color2: mainRed, location: "Home", teamName: "Oxford United", position: "14")
.padding(.leading, 15)
.padding(.trailing, 6)
teamWidget(defence: $team2Defence, midfield: $team2Midfield, attack: $team2Attack, color1: highlightBlue, color2: mainBlue, location: "Away", teamName: "Luton Town", position: "3")
.padding(.leading, 6)
.padding(.trailing, 15)
}
.padding(.top, 15)
VStack {
RadarChartView(width: 370, MainColor: Color.init(white: 0.8), SubtleColor: Color.init(white: 0.6), quantity_incrementalDividers: 4, dimensions: dimensions, data: data)
}
.frame(height: 275)
.padding(.top, 15)
Spacer() //Forces all to top in main stack
HStack {
Button(action: {}) {
Image("home")
}
Spacer()
Button(action: {}) {
Image("barcode")
}
Spacer()
Button(action: {}) {
Image("squad")
}
Spacer()
Button(action: {}) {
Image("fixtures")
}
Spacer()
Button(action: {}) {
Image("settings")
}
}
.padding(.leading, 20)
.padding(.trailing, 20)
.padding(.bottom, 15)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The net result of this looks something like this... but currently the ability bars and radar charts dont have the same single truth data set.
content view screen shot

Interact with item inside widgets iOS14

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.