I have a very simple ViewModifier but no matter what this error always happen
-> Type 'MySSSAJHKDKJLHSADKJASDKJDS' does not conform to protocol 'ViewModifier'
struct MySSSAJHKDKJLHSADKJASDKJDS: ViewModifier {
func body(content: Content) -> some View {
return content.foregroundColor(.cyan)
.font(.subheadline)
}
}
Related
I am trying to replace a standard sheet modifier with a custom one that applies the same changes to the content of all sheets as to the main view (it can be useful for changing accent color, although there is a UIKit approach for it, but specifically I want to apply privacySensitive modifier to all sheets).
The code that creates the modifiers compiles ok:
import SwiftUI
struct SheetForItem<T, C>: ViewModifier where T: Identifiable, C: View {
var item: Binding<T?>
var onDismiss: (() -> Void)? = nil
var sheetContent: (T) -> C
func body(content: Content) -> some View {
content.sheet(item: item, onDismiss: onDismiss) {
sheetContent($0).privacySensitive()
}
}
}
extension View {
func appSheet<T, Content>(
item: Binding<T?>,
onDismiss: (() -> Void)? = nil,
content: #escaping (T) -> Content
) -> some View where T: Identifiable, Content: View {
modifier(SheetForItem(item: item, onDismiss: onDismiss, sheetContent: content))
}
}
Mostly it works, but some of the usages of appSheet in the chain of other modifiers instead of sheet do not compile with an error:
Type () cannot conform to View.
The example below doesn't compile (but it will compile if I replace appSheet with sheet):
import SwiftUI
enum Actions:Identifiable {
case action1
case action2
var id: Self { self }
}
struct AppSheetExample: View {
#State var showActions = false
#State private var action: Actions?
var body: some View {
Button { showActions = true } label: {
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
}
.confirmationDialog("Actions", isPresented: $showActions, titleVisibility: .visible) {
Button("Action 1") { action = .action2 }
Button("Action 2") { action = .action2 }
}
.appSheet(item: $action) { sheet in
switch sheet {
case .action1: Text("Action 1")
case .action2: Text("Action 2")
}
}
}
}
Thank you!
You need to mark your content closure with #ViewBuilder since you're not explicitly returning a View(i.e: return Text("Action 1")):
extension View {
func appSheet<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
#ViewBuilder content: #escaping () -> Content
) -> some View where Content: View {
modifier(SheetIsPresented(isPresented: isPresented, onDismiss: onDismiss, sheetContent: content))
}
func appSheet<T, Content>(
item: Binding<T?>,
onDismiss: (() -> Void)? = nil,
#ViewBuilder content: #escaping (T) -> Content
) -> some View where T: Identifiable, Content: View {
modifier(SheetForItem(item: item, onDismiss: onDismiss, sheetContent: content))
}
}
For SWIFTUI i have created a ViewModifier to use the refreshable(action) view modifier available under iOS15.
with this modifier I don't have to embed my all view in availability check.
It's seems to work properly, but I'm not confortable with the #Sendable and I wondering if there is some performance issue.
Do you think this is a good implementation?
struct RefreshView: ViewModifier {
var action: #Sendable () async -> Void
func body(content: Content) -> some View {
if #available(iOS 15, *) {
content.refreshable(action: action)
} else {
content
}
}
}
extension View {
func refreshView(action: #escaping #Sendable () async -> Void) -> some View {
modifier(RefreshView(action: action))
}
}
I've got a custom modifier to replace navigation bar title with an image view, in iOS 14 this is pretty straightforward with the .toolbar modifier, however in iOS 13 it needs a bit more work but it's possible.
The problem comes when I want to use both solutions in a conditional modifier, the following code reproduces the issue, it works when running on iOS 14 but it produces no result on iOS 13, however if the "#available" condition is removed from the modifier leaving only iOS 13 code, it works as expected. Wrapping iOS 14 in AnyView does not help either:
extension View {
#ViewBuilder
func configuresIcon() -> some View {
if #available(iOS 14.0, *){
self.modifier(NavigationConfigurationView14Modifier())
} else {
self.modifier(NavigationConfigurationViewModifier(configure: { nv in
nv.topItem?.titleView = UIImageView(image: UIImage(named: IMAGE_NAME_HERE))
}))
}
}
}
struct NavigationConfigurationViewModifier: ViewModifier {
let configure: (UINavigationBar) -> ()
func body(content: Content) -> some View {
content.background(NavigationControllerLayout(configure: {
configure($0.navigationBar)
}))
}
}
#available(iOS 14.0, *)
struct NavigationConfigurationView14Modifier: ViewModifier {
func body(content: Content) -> some View {
content
.toolbar {
ToolbarItem(placement: .principal) {
Image(IMAGE_NAME_HERE)
}
}
}
}
struct NavigationControllerLayout: UIViewControllerRepresentable {
var configure: (UINavigationController) -> () = { _ in }
func makeUIViewController(
context: UIViewControllerRepresentableContext<NavigationControllerLayout>
) -> UIViewController {
UIViewController()
}
func updateUIViewController(
_ uiViewController: UIViewController,
context: UIViewControllerRepresentableContext<NavigationControllerLayout>
) {
if let navigationContoller = uiViewController.navigationController {
configure(navigationContoller)
}
}
}
Try to wrap content in Group (not tested - only idea)
extension View {
#ViewBuilder
func configuresIcon() -> some View {
Group {
if #available(iOS 14.0, *){
self.modifier(NavigationConfigurationView14Modifier())
} else {
self.modifier(NavigationConfigurationViewModifier(configure: { nv in
nv.topItem?.titleView = UIImageView(image: UIImage(named: IMAGE_NAME_HERE))
}))
}
}
}
}
Maybe it will work if we check OS version at runtime, not at compiletime (because buildlimitedavailability(_:) is itself available only since iOS 14).
extension View {
func erase() -> AnyView {
return AnyView(self)
}
func applyIf<VM1: ViewModifier, VM2: ViewModifier>(_ condition: #autoclosure () -> Bool, ApplyIfTrue: VM1, ApplyIfFalse: VM2
) -> AnyView {
if condition() {
return self.modifier(ApplyIfTrue).erase()
} else {
return self.modifier(ApplyIfFalse).erase()
}
}
#ViewBuilder func configuresIcon() -> some View {
self.applyIf(NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0)),
ApplyIfTrue: NavigationConfigurationView14Modifier(),
ApplyIfFalse: modifier(NavigationConfigurationViewModifier(configure: { nv in
nv.topItem?.titleView = UIImageView(image: UIImage(named: IMAGE_NAME_HERE))
})))
}
}
If you really need #available option there are several things to try. 1) Maybe it is because of inappropriate extension point, so try to move it out of View's extension. 2)Move the logic from modifiers to the body.
For example the code below work fine.
#available(macOS 10.15, iOS 13.0, *)
struct ContentView: View {
var body: some View {
ScrollView {
if #available(macOS 11.0, iOS 14.0, *) {
LazyVStack {
ForEach(1...1000, id: \.self) { value in
Text("Row \(value)")
}
}
} else {
VStack {
ForEach(1...1000, id: \.self) { value in
Text("Row \(value)")
}
}
}
}
}
}
#State var modifierEnabled : Bool
struct BlankModifier: ViewModifier {
func body(content: Content) -> some View {
content
}
}
extension View {
func TestModifierView() -> some View{
return self.modifier(BlankModifier())
}
}
How to apply TestModifierView only in case of modifierEnabled == true ?
#available(OSX 11.0, *)
public extension View {
#ViewBuilder
func `if`<Content: View>(_ condition: Bool, content: (Self) -> Content) -> some View {
if condition {
content(self)
} else {
self
}
}
}
#available(OSX 11.0, *)
public extension View {
#ViewBuilder
func `if`<TrueContent: View, FalseContent: View>(_ condition: Bool, ifTrue trueContent: (Self) -> TrueContent, else falseContent: (Self) -> FalseContent) -> some View {
if condition {
trueContent(self)
} else {
falseContent(self)
}
}
}
usage example ( one modifier ) :
Text("some Text")
.if(modifierEnabled) { $0.foregroundColor(.Red) }
usage example2 (two modifier chains related to condition) :
Text("some Text")
.if(modifierEnabled) { $0.foregroundColor(.red) }
else: { $0.foregroundColor(.blue).background(Color.green) }
BUT!!!!!!!!!!!
Important thing that this modifier can be reason of some indentity issues. (later you will understand this)
So in some cases better to use standard if construction
I like the solution without type erasers. It looks strict and elegant.
public extension View {
#ViewBuilder
func modify<TrueContent: View, FalseContent: View>(_ condition: Bool, ifTrue modificationForTrue: (Self) -> TrueContent, ifFalse modificationForFalse: (Self) -> FalseContent) -> some View {
if condition {
modificationForTrue(self)
} else {
modificationForFalse(self)
}
}
}
Usage
HStack {
...
}
.modify(modifierEnabled) { v in
v.font(.title)
} ifFalse: {
$0.background(Color.red) // even shorter
}
If you only plan to apply a modifier (or a chain of modifiers) consider this:
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public extension View {
#ViewBuilder func modifier<VM1: ViewModifier, VM2: ViewModifier>(_ condition: #autoclosure () -> Bool, applyIfTrue: VM1, applyIfFalse: VM2
) -> some View {
if condition() {
self.modifier(applyIfTrue)
} else {
self.modifier(applyIfFalse)
}
}
}
Usage is almost as simple as with regular .modifier.
...
Form {
HStack {
...
}
.modifier(modifierEnabled, applyIfTrue: CornerRotateModifier(amount: 8, anchor: .bottomLeading), applyIfFalse: EmptyModifier())
...
You can omit applyIfFalse part for conciseness and just return self.erase() if condition is false.
I just migrated to use CoreDate instead of a simple collection. I'm using iOS13 beta 8 and Xcode11 beta 6.
struct BeaconList: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: fetchRequest(), animation: nil) var beacons: FetchedResults<Beacon>
static func fetchRequest() -> NSFetchRequest<Beacon> {
let request: NSFetchRequest<Beacon> = Beacon.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
return request
}
func buildView(name: String, beacons: [Beacon]) -> AnyView {
return AnyView (
Section(header: Text(name)) {
ForEach(beacons) { beacon in
BeaconListEntry(beacon: beacon)
}
}
)
}
var body: some View {
TabView {
NavigationView {
List {
buildView(name: "MY BEACONS", beacons: beacons.filter { $0.isActive })
}
.navigationBarTitle("Beacons")
.listStyle(GroupedListStyle())
.navigationBarItems(trailing: addButton)
}
.tabItem {
Image(systemName: "antenna.radiowaves.left.and.right")
Text("Beacons")
}
}
}
and with BeaconListEntryas follows:
struct BeaconListEntry : View {
#Binding var beacon: Beacon
var body: some View {
HStack {
Text(verbatim: beacon.name!)
}
}
}
(Please ignore the forced unwrapped, it's just for testing purposes)
When I used this with collections before I rewrote, it worked, but now I get the message
Cannot invoke initializer for type 'ForEach<_, _, _>' with an argument list of type '([Beacon], #escaping (Binding<Beacon>) -> BeaconListEntry)'
and
1. Overloads for 'ForEach<_, _, _>' exist with these partially matching parameter lists: (Data, content: #escaping (Data.Element) -> Content), (Range<Int>, content: #escaping (Int) -> Content)
Any idea on where to look? Is this the correct way to use FetchedResults?
The BeaconListEntry initializer expects an argument having type Binding, so you should modify your call as follows:
BeaconListEntry(beacon: .constant(beacon))