SwiftUI macOS List Row removing padding - swift

I am trying to display a row inside my ListView in SwiftUI for MacOS.
However, my issue is that the row contains padding around it. I would like to stretch it to the boundaries of my ListView.
Apple uses .listRowInsets(EdgeInsets()) in their example. However, that is only shown on iOS. For me, it is not working on macOS.
My row got a red border to visualize the issue. I want it to be stretched all the way to the boundaries of the List Row, so fill the whole blue row.
Is that possible in macOS?
Thanks in advance.

For now I've found only workaround (.listRowInsets should really do this work, so worth submitting feedback to Apple):
struct TestListRow: View {
var body: some View {
List {
ForEach (0..<3) { i in
HStack {
Text("Test row \(i)").font(.largeTitle)
Spacer()
}
}
.listRowBackground(Color.green)
.border(Color.red)
.padding(.horizontal, -8) // << workaround !!
}.environment(\.defaultMinListRowHeight, 40)
.border(Color.yellow)
}
}

I added this code at end of the row and the padding was shrunk!!!
Spacer()
Text(" ")
.padding()
Sample Code
import SwiftUI
struct testViews1: View {
var body: some View {
List {
ForEach (0..<3) { i in
HStack {
Text("row \(i)")
Spacer()
}
.background(Color.red)
.frame(height: 30)
}
}
}
}
struct testViews2: View {
var body: some View {
List {
ForEach (0..<3) { i in
HStack {
Text("row \(i)")
Spacer()
Text(" ")
.padding()
}
.background(Color.red)
.frame(height: 30)
}
}
}
}
struct testViews_Previews: PreviewProvider {
static var previews: some View {
testViews1()
.frame(height: 150)
testViews2()
.frame(height: 150)
}
}
output
It works with SwiftUI 1.

Related

How to get different components to have different animations?

I have two simple ui components a Rectangle and an Image.
I just want a slide animation for Rectangle and scale animation for Image.
However, I got a default value animation for both of these.
Is there a problem in my code? I don't have any error. I'm using beta SF Symbols tho. Could this be the problem?
import SwiftUI
struct ContentView: View {
#State var animate: Bool = false
var body: some View {
VStack(alignment: .center) {
HStack() {
if animate {
ZStack() {
Rectangle()
.frame(height: 50)
.transition(.slide)
Image(systemName: "figure.mixed.cardio")
.foregroundColor(.white)
.transition(.scale)
}
}
}
Button("animate") {
withAnimation {
animate.toggle()
}
}
}
}
}
Transition works when specific view appeared, but in your code ZStack loads views and then appears as a whole.
Here is a fix:
HStack() {
ZStack() {
if animate { // << here !!
Rectangle()
.frame(height: 50)
.transition(.slide)
Image(systemName: "figure.mixed.cardio")
.foregroundColor(.white)
.transition(.scale)
}
}
}
The problem here is neither your SF Symbols or Compiling Error.
Animation is a bit tricky especially if you want different animations for different views inside the same stack.
To achieve your goal, you need to wrap each sub view inside an independent stack, then they will have their own unique animation.
Try this code:
import SwiftUI
struct TestView: View {
#State var animate: Bool = false
var body: some View {
VStack(alignment: .center) {
HStack() {
ZStack() {
//added
if animate {
ZStack {
Rectangle()
.frame(height: 50)
} .transition(.slide)
}
//added
if animate {
ZStack {
Image(systemName: "figure.mixed.cardio")
.foregroundColor(.white)
}.transition(.scale)
}
}
}
Button("animate") {
withAnimation {
animate.toggle()
}
}
}
}
}

How to make a List background black in SwiftUI?

What I did to make the entire screen black is to use this construction in my view:
ZStack {
Color.black.ignoresSafeArea() }
And for the List modifiers I used the following two:
.listRowBackground(Color.black)
.listStyle(.plain)
This works great.
But then I want to add a view on top of my List (like a header) and some white space appears around it ? Where does that come from ? And how can I change it to black ?
Here is my complete code:
struct WorkoutView: View {
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
List {
TempView()
ForEach(1..<30) { index in
VStack(alignment: .leading, spacing: 4) {
Text("one line")
.foregroundColor(.white)
.font(.title)
.fontWeight(.bold)
}
.padding(.vertical)
}
.listRowBackground(Color.black)
}
.listStyle(.plain)
}
}
}
struct TempView: View {
var body: some View {
ZStack(alignment: .leading) {
Color.black.ignoresSafeArea()
Text("Hello World")
.foregroundColor(.red)
.font(.largeTitle)
.bold()
}
}
}
Just do the same thing you did for the ForEach: add a .listBackground() modifier. Like this:
TempView()
.listRowBackground(Color.black)

SwiftUI: List, NavigationLink, and badges

I'm working on my first SwiftUI app, and in it would like to display a List of categories, with a badge indicating the number of items in that category. The title of the category would be on the left, and the badge would be right-aligned on the row. The list would consist of NavigationLinks so that tapping on one would drill further down into the view hierarchy. The code I've written to render the NavigationLinks looks like this:
List {
ForEach(myItems.indices) { categoryIndex in
let category = categories[categoryIndex]
let title = category.title
let fetchReq = FetchRequest<MyEntity>(entity: MyEntity(),
animation: .default)
NavigationLink(destination: MyItemView()) {
HStack(alignment: .center) {
Text(title)
Spacer()
ZStack {
Circle()
.foregroundColor(.gray)
Text("\(myItemsDict[category]?.count ?? 0)")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
}
}
}
}
While it does render a functional NavigationLink, the badge is not displayed right-aligned, as I had hoped. Instead, it looks like this:
I know I'm getting hung up on something in my HStack, but am not sure what. How do I get it so that the category title Text takes up the majority of the row, with the badge right-aligned in the row?
SwiftUI doesn't know how big your Circle should be, so the Spacer doesn't do anything. You should set a fixed frame for it.
struct ContentView: View {
var body: some View {
List {
ForEach(0..<2) { categoryIndex in
let title = "Logins"
NavigationLink(destination: Text("Hi")) {
HStack(alignment: .center) {
Text(title)
Spacer()
ZStack {
Circle()
.foregroundColor(.gray)
.frame(width: 25, height: 25) // here!
Text("5")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
}
}
}
}
}
}
Result:

Problem with SwiftUI: Vertical ScrollView with Button

I'm quite new to programming so please excuse any dumb questions. I'm trying to make a ScrollView with the content being buttons. Although the button prints to console, when shown in the simulator the button displays as a large blue rectangle rather than displaying the image I would like it to.
Code Regarding ScrollView:
struct ContentView: View {
var body: some View {
[Simulator Display][1]
VStack {
Image("logo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.leading, 50)
.padding(.trailing, 50)
.padding(.top, 20)
.padding(.bottom, -20)
Spacer()
ScrollView {
VStack(spacing: 20) {
Button(action: {
//ToDo
print("Executed")
}) {
Image("Logo")
}
}
}
}
}
}
Simulator Display:
Image(Placeholder for now) I want to be displayed:
So I tried it an yeah it was very weird. Anyway, here is an example of how you can include the image. Just take the portion of the Button and paste it
struct ContentView: View {
var body: some View {
ZStack {
Button(action: {
print("button pressed")
}) {
Image("image")
.renderingMode(.original)
}
}
}
}

Adding Segmented Style Picker to SwiftUI's NavigationView

The question is as simple as in the title. I am trying to put a Picker which has the style of SegmentedPickerStyle to NavigationBar in SwiftUI. It is just like the native Phone application's history page. The image is below
I have looked for Google and Github for example projects, libraries or any tutorials and no luck. I think if nativa apps and WhatsApp for example has it, then it should be possible. Any help would be appreciated.
SwiftUI 2 + toolbar:
struct DemoView: View {
#State private var mode: Int = 0
var body: some View {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Color", selection: $mode) {
Text("Light").tag(0)
Text("Dark").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
}
You can put a Picker directly into .navigationBarItems.
The only trouble I'm having is getting the Picker to be centered. (Just to show that a Picker can indeed be in the Navigation Bar I put together a kind of hacky solution with frame and Geometry Reader. You'll need to find a proper solution to centering.)
struct ContentView: View {
#State private var choices = ["All", "Missed"]
#State private var choice = 0
#State private var contacts = [("Anna Lisa Moreno", "9:40 AM"), ("Justin Shumaker", "9:35 AM")]
var body: some View {
GeometryReader { geometry in
NavigationView {
List {
ForEach(self.contacts, id: \.self.0) { (contact, time) in
ContactView(name: contact, time: time)
}
.onDelete(perform: self.deleteItems)
}
.navigationBarTitle("Recents")
.navigationBarItems(
leading:
HStack {
Button("Clear") {
// do stuff
}
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[$0])
}
}
.frame(width: 130)
.pickerStyle(SegmentedPickerStyle())
.padding(.leading, (geometry.size.width / 2.0) - 130)
},
trailing: EditButton())
}
}
}
func deleteItems(at offsets: IndexSet) {
contacts.remove(atOffsets: offsets)
}
}
struct ContactView: View {
var name: String
var time: String
var body: some View {
HStack {
VStack {
Image(systemName: "phone.fill.arrow.up.right")
.font(.headline)
.foregroundColor(.secondary)
Text("")
}
VStack(alignment: .leading) {
Text(self.name)
.font(.headline)
Text("iPhone")
.foregroundColor(.secondary)
}
Spacer()
Text(self.time)
.foregroundColor(.secondary)
}
}
}
For those who want to make it dead center, Just put two HStack to each side and made them width fixed and equal.
Add this method to View extension.
extension View {
func navigationBarItems<L, C, T>(leading: L, center: C, trailing: T) -> some View where L: View, C: View, T: View {
self.navigationBarItems(leading:
HStack{
HStack {
leading
}
.frame(width: 60, alignment: .leading)
Spacer()
HStack {
center
}
.frame(width: 300, alignment: .center)
Spacer()
HStack {
//Text("asdasd")
trailing
}
//.background(Color.blue)
.frame(width: 100, alignment: .trailing)
}
//.background(Color.yellow)
.frame(width: UIScreen.main.bounds.size.width-32)
)
}
}
Now you have a View modifier which has the same usage of navigationBatItems(:_). You can edit the code based on your needs.
Usage example:
.navigationBarItems(leading: EmptyView(), center:
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}, trailing: EmptyView())
UPDATE
There was the issue of leading and trailing items were violating UINavigationBarContentView's safeArea. While I was searching through, I came across another solution in this answer. It is little helper library called SwiftUIX. If you do not want install whole library -like me- I created a gist just for navigationBarItems. Just add the file to your project.
But do not forget this, It was stretching the Picker to cover all the free space and forcing StatusView to be narrower. So I had to set frames like this;
.navigationBarItems(center:
Picker(...) {
...
}
.frame(width: 150)
, trailing:
StatusView()
.frame(width: 70)
)
If you need segmentcontroll to be in center you need to use GeometryReader, below code will provide picker as title, and trailing (right) button.
You set up two view on the sides left and right with the same width, and the middle view will take the rest.
5 is the magic number depends how width you need segment to be.
You can experiment and see the best fit for you.
GeometryReader {
Text("TEST")
.navigationBarItems(leading:
HStack {
Spacer().frame(width: geometry.size.width / 5)
Spacer()
picker
Spacer()
Button().frame(width: geometry.size.width / 5)
}.frame(width: geometry.size.width)
}
But better solution is if you save picker size and then calculate other frame sizes, so picker will be same on ipad & iphone
#State var segmentControllerWidth: CGFloat = 0
var body: some View {
HStack {
Spacer()
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
.background(Color.red)
segmentController
.fixedSize()
.background(PreferenceViewSetter())
profileButton
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
}
.onPreferenceChange(PreferenceViewKey.self) { preferences in
segmentControllerWidth = preferences.width
}
}
struct PreferenceViewSetter: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: PreferenceViewKey.self,
value: PreferenceViewData(width: geometry.size.width))
}
}
}
struct PreferenceViewData: Equatable {
let width: CGFloat
}
struct PreferenceViewKey: PreferenceKey {
typealias Value = PreferenceViewData
static var defaultValue = PreferenceViewData(width: 0)
static func reduce(value: inout PreferenceViewData, nextValue: () -> PreferenceViewData) {
value = nextValue()
}
}
Simple answer how to center segment controller and hide one of the buttons.
#State var showLeadingButton = true
var body: some View {
HStack {
Button(action: {}, label: {"leading"})
.opacity(showLeadingButton ? true : false)
Spacer()
Picker(selection: $selectedStatus,
label: Text("SEGMENT") {
segmentValues
}
.id(UUID())
.pickerStyle(SegmentedPickerStyle())
.fixedSize()
Spacer()
Button(action: {}, label: {"trailing"})
}
.frame(width: UIScreen.main.bounds.width)
}