I have created a ViewModifier that adds a icon to the right of a its content, the way I want the icon to appear is by animating the .clipShape() modifier from -50 to 0, the problem is that when appearing the first time, it just pops out with no animation and the same thing happens when disappearing for the last time. At the bottom you'll find a video demonstration
My ViewModifier so far
extension View {
func addRightIcon(icon: Image, show: Bool) -> some View {
return modifier(RightIconModifier(icon: icon, show: show))
}
}
struct RightIconModifier: ViewModifier {
var icon: Image
private var iconMask: Int = 0
init(icon: Image, show: Bool) {
self.icon = icon
withAnimation(Animation.interpolatingSpring(stiffness: 170, damping: 15).delay(2.5)) {
iconMask = show ? 0 : -50
}
}
func body(content: Content) -> some View {
ZStack {
content
.overlay(rightIcon)
}
}
var rightIcon: some View {
icon
.font(.system(size: 25))
.foregroundColor(.black)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .trailing)
.padding()
.clipShape(Rectangle().offset(x: CGFloat(iconMask)))
}
}
This would be a short version of how I'm using it, hopefully you get an idea to make it work
TextField(placeholder, text: $text).addRightIcon(icon: Image(systemName: "checkmark"), show: isTextValid)
var isTextValid: Bool {
if !text.isEmpty {
let validation = NSPredicate(format: "SELF MATCHES %#", "[’a-zA-Z]{3,20}")
let validated = validation.evaluate(with: text)
return validated
}
return false
}
This is a video demonstration
Animatable modifiers should be inside body (directly or called from within body), but not in init. Modifier is also a struct, so if its properties modified externally they are also animatable.
So here is fixed ViewModifier. Tested with Xcode 14 / iOS 16
Note: I simplified animation and filter for testing purpose
struct RightIconModifier: ViewModifier {
var icon: Image
var show: Bool // << injected changes
func body(content: Content) -> some View {
ZStack {
content
.overlay(rightIcon)
}
}
var rightIcon: some View {
icon
.font(.system(size: 25))
.foregroundColor(.black)
.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .trailing)
.padding()
.clipShape(Rectangle().offset(x: CGFloat(show ? 0 : -50))) // << switch is here !!
.animation(.easeIn(duration: 1), // << simplified for testing
value: show)
}
}
Test module on GitHub
I have a simple Core Data macOS app, with its Data Model as follow:
Entity:
Data
Attributes:
text String
starred Boolean
date Date
I'm trying to access the data from a custom menu I created, iterate it and (in the future) save it to disk.
I've been trying to add code directly to the menu item created as:
#main
struct My_DataApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.frame(minWidth: 560, maxWidth: .infinity,
minHeight: 300, maxHeight: .infinity)
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
}
}
.windowToolbarStyle(UnifiedWindowToolbarStyle(showsTitle: false))
.commands{
CommandGroup(after: CommandGroupPlacement.newItem) {
Button("Export Data") {
#FetchRequest(entity: Data.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Data.date, ascending: false)], animation: .default)
var DataItems: FetchedResults<Data>
for data in DataItems {
print("\(String(describing: data.text))")
}
}
}
}
}
}
Of course Xcode is complaining:
I understand the error but I don't know how else to think about accessing the data from the menu.
I saw a few posts here in SO about the same error, and I tried to add code to the .onAppear section but same error happens there.
You can use NSFetchRequest directly and execute it by view context, like
Button("Export Data") {
let fetchRequest = Data.fetchRequest()
// .. set up parameters
let context = self.persistenceController.container.viewContext
if let result = try? context.fetch(fetchRequest) {
for data in result {
print("\(String(describing: data.text))")
}
}
}
I'm having an .onDrag on an Image. Whether I provide a preview myself or not, the preview always gets stuck to show the Image that appeared on the first drag. The NSItemProvider itself works well and I access the proper data, but not within the preview.
A simple example (code available here: https://github.com/godbout/DragPreview)
import SwiftUI
struct AppDetail {
let bundleIdentifier: String
let icon: NSImage
init(bundleIdentifier: String) {
self.bundleIdentifier = bundleIdentifier
if
let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleIdentifier),
let representation = NSWorkspace.shared.icon(forFile: url.path).bestRepresentation(for: NSRect(x: 0, y: 0, width: 64, height: 64), context: nil, hints: nil)
{
self.icon = NSImage(size: representation.size)
self.icon.addRepresentation(representation)
} else {
self.icon = NSApp.applicationIconImage!
}
}
}
struct ContentView: View {
#State private var apps: [AppDetail] = [
AppDetail(bundleIdentifier: "com.apple.Mail"),
AppDetail(bundleIdentifier: "com.apple.MobileSMS"),
AppDetail(bundleIdentifier: "com.apple.Safari"),
]
var body: some View {
VStack(alignment: .center) {
Image(nsImage: apps.first == nil ? NSApp.applicationIconImage! : apps.first!.icon)
.onDrag {
guard
let app = apps.first,
let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: app.bundleIdentifier)
else {
return NSItemProvider()
}
return NSItemProvider(object: url as NSURL)
} preview: {
Text(apps.first == nil ? "no app" : apps.first!.bundleIdentifier)
Image(nsImage: apps.first == nil ? NSApp.applicationIconImage! : apps.first!.icon)
}
Button("next") {
apps.removeFirst()
}
.disabled(apps.isEmpty)
}
.frame(width: 200, height: 200)
.padding()
}
}
Anything I'm doing wrong? Could this be a(nother) SwiftUI bug? If the preview is actually static and only rendered at the first drag, how could I change the code so that I get the preview of my current Image?
Thanks.
I read somewhere in a comment by #Asperi that the drag preview is only created once at init. So if you add an .id to the image it forces a redraw and reinit of preview:
Image(nsImage: apps.first == nil ? NSApp.applicationIconImage! : apps.first!.icon)
.id(apps.first?.bundleIdentifier) // here
.onDrag { ...
Im trying to make Augmented Reality app with RealityKit but ContentView.swift I have some problems
What is missing here ?` You can see errors on picture which I shared. I followed some tutorial so Im new on Xcode and Realitykit.
Failed to produce diagnostic for expression; please file a bug report
Cannot find 'PlacementButtonsView' in scope
import SwiftUI
import RealityKit
struct ContentView : View {
var models: [String] = {
let filemanager = FileManager.default
guard let path = Bundle.main.resourcePath, let files = try?
filemanager.contentsOfDirectory(atPath:path) else
{ return[]
}
var avaliableModels: [String] = []
for filename in files where filename.hasSuffix("usdz") {
let modelName = filename.replacingOccurrences(of: ".usdz", with: "")
avaliableModels.append(modelName)
}
return avaliableModels
}()
var body: some View {
ZStack(alignment: .bottom) {
ARViewContainer()
ModelPickerView(models: self.models)
PlacementButtonsView()
}
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {}
}
struct ModelPickerView: View {
var models: [String]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(0 ..<
self.models.count) { index in
Button(action: {
print("DEBUG: selected model with name: \(self.models[index])")
}) {
Image(uiImage: UIImage(named: self.models[index])!)
.resizable()
.frame(height: 60)
.aspectRatio(1/1,contentMode: .fit)
.background(Color.white)
.cornerRadius(12)
}
.buttonStyle (PlainButtonStyle())
}
}
}
.padding(15)
.background(Color.black.opacity(0.5))
}
struct PlacementButtonsView: View {
var body: some View {
HStack {
//Cancel Button
Button(action: {
print("DEBUG: model placement canceled.")
}) {
Image(systemName: "xmark")
.frame(width: 60, height: 60)
.font(.title)
.background(Color.white.opacity(0.75))
.cornerRadius(30)
.padding(20)
}
//Confirm Button
Button(action: {
print("DEBUG: model placement confirmed.")
}) {
Image(systemName: "checkmark")
.frame(width: 60, height: 60)
.font(.title)
.background(Color.white.opacity(0.65))
.cornerRadius(30)
.padding(20)
}
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
}
Check your braces. The PlacementButtonsView struct is actually nested within the ModelPickerView struct, and not globally available, hence why it is not available from your ContentView.
By the way, in Xcode, you can find this out by option clicking on the declaration of PlacementButtonsView:
ModelPickerView.PlacementButtonsView shows you what went wrong here; PlacementButtonsView is nested within ModelPickerView. This is why you seem to have a strange closing brace on the final line of your code sample - the same issue occurs with the preview, as it is also nested in ModelPickerView.
To make this issue more visible, and see similar issues like this in the future more easily, you can also have Xcode indent your code for you by selecting all (Cmd + A) and then pressing Control + I. You'll see the PlacementButtonsView struct indent, making it more clear that it is not globally available.
So the question is pretty simple and it's in the title. I want to remove the line separator in SwiftUI iOS 14. Previously, I was using
UITableView().appearance().separatorStyle = .none
and that used to do the job in iOS 13. Now however, it doesn't work. Any update or idea on how to make it work. Thanks:)
Here is a demo of possible solution. Tested with Xcode 12b.
List {
ForEach(0..<3) { _ in
VStack {
Text("Hello, World!").padding(.leading)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets())
.background(Color(UIColor.systemBackground)))
}
}
Merged #asperi, #akmin and #zrfrank answer into one thing. In my experience List is more reliable and efficient than LazyVStack, so I use still use List for anything complex requiring reliability.
extension View {
func listRow() -> some View {
self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: -1, bottom: -1, trailing: -1))
.background(Color(.systemBackground))
}
}
List {
Color.red
.listRow()
Color.green
.listRow()
}
How I made a list that works on both iOS 14 and iOS 13, It shows no separators and extra margins
struct NoButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
struct ListWithoutSepatorsAndMargins<Content: View>: View {
let content: () -> Content
var body: some View {
if #available(iOS 14.0, *) {
ScrollView {
LazyVStack(spacing: 0) {
self.content()
}
.buttonStyle(NoButtonStyle())
}
} else {
List {
self.content()
}
.listStyle(PlainListStyle())
.buttonStyle(NoButtonStyle())
}
}
}
Sample Usage -
ListWithoutSepatorsAndMargins {
ForEach(0..<5) { _ in
Text("Content")
}
}
in case you've more components in list, wrap them in Group
ListWithoutSepatorsAndMargins {
Group {
self.groupSearchResults()
self.myGroups()
self.exploreGroups()
}
}
}
Hope this helps someone, I wasted a lot of time in such minor thing, Apple is trying to push us hard to use LazyVStack, it seems
iOS 15:
This year Apple introduced a new modifier .listRowSeparator that can be used to style the separators. you can pass .hidden to hide it:
List(items, id:\.self) {
Text("Row \($0)")
.listRowSeparator(.hidden)
}
🌈 Also you can set each separator to any color by settings listRowSeparatorTintColor as I mentioned here in this answer:
iOS 14
Follow the answer here
I found this simple solution on the Apple Developer forums. It's working for me on 14.4:
List {
...
}.listStyle(SidebarListStyle())
This seems to add a tiny bit of padding around the edges. If that's a problem for you, you could try some negative padding.
Based on average Joe's answer I ended up with the following modifier:
struct ListSeparatorNone: ViewModifier {
var backgroundColor: Color = Color(.systemBackground)
func body(content: Content) -> some View {
content
.listRowInsets(EdgeInsets(top: -1, leading: 0, bottom: 0, trailing: 0))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.background(backgroundColor)
}
}
The view extension:
extension View {
func listSeparatorNone(backgroundColor: Color = Color(.systemBackground)) -> some View {
self.modifier(ListSeparatorNone(backgroundColor: backgroundColor))
}
}
Usage example:
List {
ForEach(viewModel.countries, id: \.self) { country in
Text(country)
.padding(.leading, 10)
}
.listSeparatorNone()
}
If you don't have a lot of cells, and therefore don't need to rely on a LazyVStack for performance, you can fallback to a ScrollView + VStack:
ScrollView {
VStack {
Row1()
Row2()
Row3()
}
}
You can also call this function at the end of your VStack (that is inner the List).
It will be an overlay on List Seperator on iOS 14 :)
private func hideDefaultListSeperator() -> some View {
Rectangle()
.fill(colorScheme == .light ? Color.white : Color.black)
.frame(maxHeight: 1)
}
Update:
I figured out a solution that works on both iOS 13 and iOS 14 and gives a simple list and uses List on both iOS.
struct ListWithoutSepatorsAndMargins<Content>: View where Content: View {
let content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
var body: some View {
List {
self.content()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.listRowInsets(EdgeInsets())
.background(Color.white)
}
.listStyle(PlainListStyle())
.buttonStyle(NoButtonStyle())
}
}
struct NoButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
and do the following in SceneDelegate.swift to remove default grey selection of cells
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
UITableView.appearance().separatorStyle = .none
UITableView.appearance().allowsSelection = false
.......
and we can use it the following way
ListWithoutSepatorsAndMargins {
ForEach(0..<5) { _ in
Text("Content")
}
}
ListWithoutSepatorsAndMargins {
Group {
self.groupSearchResults()
self.myGroups()
self.exploreGroups()
}
}
}
Here is my solution for iOS 14:
struct MyRowView: View {
var body: some View {
ZStack(alignment: .leading) {
// Background color of the Row. It will spread under the entire row.
Color(.systemBackground)
NavigationLink(destination: Text("Details")) {
EmptyView()
}
.opacity(0) // Hide the Disclosure Indicator
Text("Go to Details").padding(.leading)
}
// These 2 lines hide the row separators
.padding(.horizontal, -16) // Removes default horizontal padding
.padding(.vertical, -6) // Removes default vertical padding
}
}
The enclosing List should have this modifier
.listStyle(PlainListStyle())
The upside of this solution over using a LazyVStack is that you can still use the Edit capabilities of the List.
This solution relies unfortunately on hard-coded values to remove the system default paddings on each row. Hopefully SwiftUI 3.0 will provide simple .separatorStyle(.none) and .accessoryType(.none) modifiers.
The code to remove the Disclosure Indicators comes from: https://www.appcoda.com/hide-disclosure-indicator-swiftui-list/
Thank for #asperi, #akmin and #zrfrank and #averageJoe 's answers.
Here is another improved method works in iOS 14 and 15.
extension View {
func hideListRowSeperator() -> some View {
if #available(iOS 15, *) {
return AnyView(self.listRowSeparator(.hidden))
} else {
return AnyView(self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: -1, bottom: -1, trailing: -1))
.background(Color(.systemBackground)))
}
}
}
Use example
var body: some View {
List {
ForEach(0..<3) { _ in
Text("Hello, World!")
.padding(.leading)
.hideListRowSeperator()
}
}
.listStyle(.plain)
}
The above answer work for me, you have to set only below both function:
.listRowInsets(EdgeInsets())
.background(Color.white)