I'm trying to get my navigation view style to be stacked on iPad but default on iPhone.
Code:
.navigationViewStyle(UIDevice.current.userInterfaceIdiom == .pad ? StackNavigationViewStyle() : DefaultNavigationViewStyle())
Giving me the error:
Result values in '? :' expression have mismatching types 'StackNavigationViewStyle' and 'DefaultNavigationViewStyle'
Are these not both NavigationViewStyle subclasses?
I recommend to extract it into simple wrapper modifier and use it in place where needed. Here is modifier:
Update:
extension View {
#ViewBuilder
public func currentDeviceNavigationViewStyle() -> some View {
if UIDevice.current.userInterfaceIdiom == .pad {
self.navigationViewStyle(StackNavigationViewStyle())
} else {
self.navigationViewStyle(DefaultNavigationViewStyle())
}
}
}
SwiftUI 1.0 (backward-compatible)
extension View {
public func currentDeviceNavigationViewStyle() -> AnyView {
if UIDevice.current.userInterfaceIdiom == .pad {
return AnyView(self.navigationViewStyle(StackNavigationViewStyle()))
} else {
return AnyView(self.navigationViewStyle(DefaultNavigationViewStyle()))
}
}
}
Thats because ? : should always return values with same type.
You can implement a custom conditional modifier extension like this:
extension View {
public func modify<T, U>(if condition: Bool, then modifierT: T, else modifierU: U) -> some View where T: ViewModifier, U: ViewModifier {
Group {
if condition {
modifier(modifierT)
} else {
modifier(modifierU)
}
}
}
}
Implement custom modifiers like this:
struct IPadNavigationViewStyle: ViewModifier {
func body(content: Content) -> some View { content.navigationViewStyle(StackNavigationViewStyle()) }
}
struct IPhoneNavigationViewStyle: ViewModifier {
func body(content: Content) -> some View { content.navigationViewStyle(DefaultNavigationViewStyle()) }
}
and then use it like:
.modify(if: UIDevice.current.userInterfaceIdiom == .pad, then: IPadNavigationViewStyle(), else: IPhoneNavigationViewStyle() )
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 months ago.
Improve this question
When the getBadge() method is called from the view, why it uses the function from the extension without Status == OpenAccessPointState condition, if the Status.self is OpenAccessPointState in runtime?
Here is the code:
protocol BadgeStateViewRepresentable: Statusable {
func getBadge() -> BadgeWithText?
}
protocol Statusable {
associatedtype Status: AccessPointState
var status: Status { get }
}
extension View where Self: BadgeStateViewRepresentable {
func getBadge() -> BadgeWithText? {
return nil
}
}
extension View where Self: BadgeStateViewRepresentable, Status == OpenAccessPointState {
func getBadge() -> BadgeWithText? {
return BadgeWithText()
}
}
struct SomeDeviceDetailsView: View, BadgeStateViewRepresentable {
var status: some AccessPointState {
return OpenAccessPointState()
}
var body: some View {
getBadge()
}
}
Is there way to make this working?
If you actually declare the type of status explicitly, then it works as expected:
var status: OpenAccessPointState {
return OpenAccessPointState()
}
This is because opaque types are opaque. SomeDeviceDetailsView.Status is some AccessPointState, and you cannot "see through" what specific AccessPointState it is. Therefore, it does not fulfil the requirement Status == OpenAccessPointState.
This has nothing to do with what happens "at runtime". SomeDeviceDetailsView.Status is a type, not a variable, and it doesn't become something different at runtime. compared to at compile time.
A simpler example:
protocol Foo {
associatedtype MyType
func foo() -> MyType
}
extension Foo where MyType == Int {
func bar() { }
}
struct Impl: Foo {
func foo() -> some CustomStringConvertible {
1
}
}
// Referencing instance method 'bar()' on 'Foo' requires the types 'some CustomStringConvertible' and 'Int' be equivalent
Impl().bar()
If you are trying to dynamically dispatch getBadge, you should put it in each of the status types, and get rid of the Statusable protocol whose purpose becomes rather unclear. Example:
protocol AccessPointState {
func getBadge() -> BadgeWithText?
}
extension AccessPointState {
func getBadge() -> BadgeWithText? {
nil
}
}
struct OpenAccessPointState: AccessPointState {
func getBadge() -> BadgeWithText? {
BadgeWithText()
}
}
struct SomeOtherState: AccessPointState {}
struct SomeDeviceDetailsView: View {
var status: AccessPointState {
if Bool.random() {
return OpenAccessPointState()
} else {
return SomeOtherState()
}
}
var body: some View {
status.getBadge()
}
}
I am trying to assign a custom label style based on my icon alignment value, that is left or right. if it is left, the icon comes before text, if it is right the icon comes after it. These are my custom label styles:
var iconAlignment: IconAlignment
enum IconAlignment {
case left
case right
}
struct IconFirstLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.icon
configuration.title
}
}
}
struct IconFirstLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.title
configuration.icon
}
}
}
I want to do this now:
Label { Text("hi") } icon : {Image(systemName: "plus")}
.labelStyle(iconAlignment == .left ? IconFirstLabelStyle() : TextFirstLabelStyle())
but this causes an issue and Xcode tells me these are two different structs, even though they are inheriting the same protocol. Is there any way to use ternary here, or should I go a long approach with if else?
Ternary operator requires both parts to be of the same type.
A simple solution for this is just to use one style type and inject condition via argument, like
Label { Text("hi") } icon : {Image(systemName: "plus")}
.labelStyle(IconAlignedLabelStyle(alignIcon: iconAlignment))
struct IconAlignedLabelStyle: LabelStyle {
var alignIcon = IconAlignment.left // << default one !!
func makeBody(configuration: Configuration) -> some View {
HStack {
if let alignIcon == .left {
configuration.icon
configuration.title
} else {
configuration.title
configuration.icon
}
}
}
}
You say "even though they are inheriting the same protocol"—that does not display the understanding that some, and the ternary operator, each require one type. Such as this:
labelStyle(
iconAlignment == .left
? AnyStyle(IconFirstLabelStyle())
: AnyStyle(TextFirstLabelStyle())
)
import SwiftUI
/// A type-erased "`Style`".
public struct AnyStyle<Configuration> {
private let makeBody: (Configuration) -> AnyView
}
// MARK: - public
public extension AnyStyle {
init(makeBody: #escaping (Configuration) -> some View) {
self.makeBody = { .init(makeBody($0)) }
}
func makeBody(configuration: Configuration) -> some View {
makeBody(configuration)
}
}
// MARK: - LabelStyle
extension AnyStyle: LabelStyle where Configuration == LabelStyleConfiguration {
public init(_ style: some LabelStyle) {
self.init(makeBody: style.makeBody)
}
}
// MARK: - ButtonStyle
extension AnyStyle: ButtonStyle where Configuration == ButtonStyleConfiguration {
public init(_ style: some ButtonStyle) {
self.init(makeBody: style.makeBody)
}
}
// etc.
I want to implement a modifier setting for Texts in a similar way as it already exists for Buttons.
Aka:
Button( ... )
.buttonStyle(.plain) // <-- .plain and not PlainStyle()
Problem
Of course I cannot use an opaque which is not really the same. If it would be a View I could wrap it in an AnyView but for ViewModifiers I need another solution.
Error: Function declares an opaque return type,but the return statements in its body do not have matching underlying types
Maybe it is a bonus idea to have something like a .textStyle(.title) modifier but in my eyes, it could reduce my code to write enormously.
Source
struct TitleStyle: ViewModifier {
func body(content: Content) -> some View {
...
}
}
struct BodyStyle: ViewModifier {
func body(content: Content) -> some View {
...
}
}
enum TextStyle {
case title
case body
// Error: Function declares an opaque return type,
// but the return statements in its body do not have matching underlying types
var modifier: some ViewModifier {
switch self
{
case .title: return TitleStyle()
case .body: return BodyStyle()
}
}
}
It works different way. As all this is around generics we need to restrict declarations for known concrete types.
So, having TitleStyle and BodyStyle declared and concrete, we can specify
extension ViewModifier where Self == TitleStyle {
static var title: TitleStyle { TitleStyle() }
}
extension ViewModifier where Self == BodyStyle {
static var body: BodyStyle { BodyStyle() }
}
and then declare extension to use above like
extension View {
func textStyle<Style: ViewModifier>(_ style: Style) -> some View {
ModifiedContent(content: self, modifier: style)
}
}
so as a result we can do as demo
struct Demo_Previews: PreviewProvider {
static var previews: some View {
Text("Demo")
.textStyle(.title)
}
}
Prepared with Xcode 13.4 / iOS 15.5
Test module in GitHub
I'm using this .if extension which works great and should be added to SwiftUI, however it won't work in this case to check #available because #available may only be used as condition of an 'if', 'guard' or 'while' statement How can I make it work with this .if, anyway?
//Does not compile for #available may only be used as condition of an 'if', 'guard' or 'while' statement
ForEach...
.if(#available(iOS 15.0, *)) { $0.refreshable {
} }
extension View {
#ViewBuilder
func `if`<Transform: View>(
_ condition: Bool,
transform: (Self) -> Transform
) -> some View {
if condition {
transform(self)
} else {
self
}
}
}
I would typically just do .if(true) and then check availability inside the closure. Unfortunately, we can't check for availability in the if modifier.
Example:
struct ContentView: View {
var body: some View {
List {
Text("Hello")
/* More list items... */
}
.if(true) {
if #available(iOS 15, *) {
$0.refreshable {
//
}
} else {
$0
}
}
}
}
Added #ViewBuilder to transform parameter so you can return different View types in the if:
extension View {
#ViewBuilder func `if`<Transform: View>(_ condition: Bool, #ViewBuilder transform: (Self) -> Transform) -> some View {
if condition {
transform(self)
} else {
self
}
}
}
I am trying to create a somewhat elegant navigation system for my App. Below is a function that attempts to return a View type. This does not compile with:
func getView(view: String) -> View {
switch view {
case "CreateUser":
return CreateNewsView()
default:
return nil
}
}
The above results in a compile error: Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
Thank you for your help.
As of Swift 5.3 #hồng-phúc Answer is somehow right, just needs adding the #ViewBuilder Property explicitly.
#ViewBuilder func getView(view: String) -> some View {
switch view {
case "CreateUser":
Text(view)
case "Abc":
Image("Abc")
default:
EmptyView()
}
}
Side note: Please avoid using String Literals. Better use an enum.
I managed to fix this by using the AnyView() wrapper:
func getView(view: String?) -> AnyView {
switch view {
case "CreateUser":
return AnyView(CreateNewsView())
default:
return AnyView(EmptyView())
}
}
Making AnyView() wrapper
func getView(view: String?) -> AnyView {
if view == "CreateUser" {
return AnyView(CreateNewsView())
}
return AnyView(EmptyView())
}
You should return some View
EX:
func getView(view: String) -> some View {
return YourView()
}
for more detail a bout some View, view this
I use the extension .eraseToAnyView () to make func with some View easier:
extension View {
public function eraseToAnyView () -> AnyView {
AnyView (on its own)
}
}
The final solution will look like this:
func getView (view: String?) -> AnyView {
if view == "CreateUser" {
return CreateNewsView().eraseToAnyView()
}
return EmptyView().eraseToAnyView()
}
I think this is the most concise solution.