I have a SwiftUI View where the body looks like this
VStack {
VStack(spacing: 4) {
Text("Outline")
Button("Tap Me") {
print("tapped")
}
.buttonStyle(.outline)
}
VStack(spacing: 4) {
Text("Simple")
Button("Tap Me") {
print("tapped")
}
.buttonStyle(.simple)
}
}
So to remove the duplication, I want to do something like this:
func textButton(title: String, buttonStyle: ButtonStyle) -> some View {
VStack {
Text(title)
Button("hello") {
print("tapped")
}
.buttonStyle(buttonStyle)
}
}
The compiler asks me to add 'any' before ButtonStyle. When I do that I get the following:
"Type 'any View' cannot conform to 'View'"
It works fine if I don't pass in the ButtonStyle. Any suggestions on how to pass a ButtonStyle into a func?
thanks, in advance!
Post
Replies
Boosts
Views
Activity
I should preface my question by saying I love this formatter and the edge cases it handles for us, but I've found a case that seems odd to me.
(all times are in the same time zone. let's go with Pacific)
current time is 3:05pm, December 21
comparison time formatter output
dec 20 @ 3:15pm 23 hours ago (great!)
dec 20 @ 2:15pm yesterday (also great!)
dec 19 @ 3:15pm yesterday (wait, wut?!)
If today is Dec 21, shouldn't yesterday be limited to Dec 20 00:00:00 - 23:59:59?
I see three options:
I'm using RelativeDateTimeFormatter incorrectly
I'm misunderstanding the sematics of 'yesterday' ( is it correct to refer to 47 hours ago as yesterday?)
RelativeDateTimeFormatter is misbehaving
all thoughts on this are welcome.foun
here is some playground code demonstrating this behaviour
let relativeDateFormatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
return formatter
}()
func startDate(from dateText: String) -> Date {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
formatter.timeZone = .current
return formatter.date(from: dateText)!
}
let myNow = startDate(from: "2022-12-21T23:05:00Z")
let almostTwoDaysAgo = myNow.addingTimeInterval(-(48 * 3600 - 600))
let relText = relativeDateFormatter.localizedString(for: almostTwoDaysAgo, relativeTo: myNow)
Hello,
I'm attempting to create a basic showMore/showLess control at the bottom of a large block of text. However it is currently animating in a suboptimal way.
When I tap to expand my text block:
the text block expands right away
the text label changes from 'More' to 'Less' right away
the chevron animates both the rotationEffect and its change in position
The problem is animating the change of position is odd as it is moving 'over' the expanded text block.
My fallback position is to remove the rotationEffect and have the Image(systemName:) choose either "chevron.up" or "chevron.down" depending on is expanded.
But is there a way to either:
animate the rotationEffect, but have the position change instantly -or-
have the more/less label and the chevron both animate the position change together?
thanks in advance for any help
struct ContentView: View {
@State private var isExpanded = false
let text = "That you have wronged me doth appear in this: You have condemned and noted Lucius Pella For taking bribes here of the Sardians, wherein my letters, praying on his side because I knew the man, were slighted off."
var body: some View {
VStack(alignment: .leading) {
Text(text)
.frame(maxWidth: 300)
.lineLimit(isExpanded ? nil : 2)
footer
}
.padding()
}
@ViewBuilder
private var footer: some View {
HStack {
Text(isExpanded ? "Less" : "More")
Button {
isExpanded.toggle()
} label: {
Image(systemName: "chevron.up")
.rotationEffect(isExpanded ? .degrees(180) : .zero)
}
}
}
}
The following code:
Swift
Text("Hello, world!")
.redacted(reason: .placeholder)
creates a rectangle with rounded corners. Is there any way to reduce/eliminate the corner rounding?
thanks in advance
Hey fellow devs,
I have a parent SwiftUI view that includes an SWAttributionView. When I add an .onTapGesture{ } modifier to my parent view, the SWAttributionView no longer appears to 'see' taps. They instead go to the parent's tapGesture modifier.
I think I'd be more willing to just accept this, if it wasn't possible for its SwiftUI sibling views to be able to 'see' their taps.
Here is some sample code:
struct ContentView: View {
@State var tapType: String?
var body: some View {
RowView(accessory: {
Button {
tapType = "Globe"
} label: {
Image(systemName: "globe")
.foregroundColor(.accentColor)
}
})
.onTapGesture {
tapType = "Row"
}
.alert(item: $tapType) { tt in
Alert(title: Text("tap type: \(tt)"))
}
}
}
struct RowView<Accessory: View>: View {
private let accessory: () -> Accessory
init(accessory: @escaping () -> Accessory) {
self.accessory = accessory
}
var body: some View {
HStack {
VStack {
Text("Row Text")
if let highlight = SharedWithYouMonitor.shared.highlights.first {
SwiftUIAttributionView(highlight: highlight)
.frame(maxHeight: 100)
}
}
accessory()
}
.padding()
}
}
The above code is able to set tapType to 'globe' by tapping the globe button. It is also able to set tapType to 'row' by tapping anywhere else in the RowView. But it is not able to demonstrate the sharedWithYou tap behaviour.
When I comment out the tapType = 'row' onTapGesture modifier, the SWAttributeView behaves as expected.
Is this a bug/limitation in SWAttributeView that prevents it from working correctly when its parent has an .onTapGesture modifier?
Thanks in advance for any assistance :-)
here is some other supporting code, that's not specifically germane to my question, but is needed to get things running.
import SharedWithYou
public class SharedWithYouMonitor: NSObject {
private let highlightCenter: SWHighlightCenter
public static let shared = SharedWithYouMonitor()
public var localizedTitle: String {
return SWHighlightCenter.highlightCollectionTitle
}
public var highlights: [SWHighlight] {
return highlightCenter.highlights
}
override init() {
self.highlightCenter = SWHighlightCenter()
super.init()
highlightCenter.delegate = self
}
}
extension SharedWithYouMonitor: SWHighlightCenterDelegate {
public func highlightCenterHighlightsDidChange(_ highlightCenter: SWHighlightCenter) {
highlightCenter.highlights.forEach { highlight in
print("Received a new highlight with URL: \(highlight.url)")
}
}
}
struct SwiftUIAttributionView: UIViewRepresentable {
private let highlight: SWHighlight
init(highlight: SWHighlight) {
self.highlight = highlight
}
func makeUIView(context: Context) -> SWAttributionView {
let result = SWAttributionView()
result.highlight = highlight
result.displayContext = .summary
result.horizontalAlignment = .center
result.preferredMaxLayoutWidth = 300
return result
}
func updateUIView(_ uiView: SWAttributionView, context: Context) {
}
}
extension String: Identifiable {
public typealias ID = Int
public var id: Int {
return hash
}
}
Hi Fellow devs,
This is only nominally related to the Vision SDK. I want to detect faces and update my UI based on the results.
The code below does not compile. The line that begins with let request = throws the following error:
Escaping closure captures mutating 'self' parameter
I understand the problem with trying to modify a struct from within a closure, but I don't know what I'd need to change to be able to update the UI, based on the results from the face detection request.
Any suggestions?
thanks, in advance
import SwiftUI
import Vision
struct ContentView: View {
@State var uiImage = UIImage(named: "Ollie")!
@State var faceCount = 0
init() {
let handler = VNImageRequestHandler(cgImage: uiImage.cgImage!, options: [:])
let request = VNDetectFaceRectanglesRequest { _, _ in
self.faceCount = 3
}
try! handler.perform([request])
}
var body: some View {
VStack {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fit)
Text("faces: \(faceCount)")
}
.padding()
}
}
Hey fellow devs,
I just wanted to make sure I understand the differences between these two requests.
It appears both these requests return pitch, yaw and roll in the VNFaceObservation's they return.
However, VNDetectFaceCaptureQualityRequest's returned VNFaceObservation's include faceCaptureQuality values, while these values are nil in the response from VNDetectFaceRectanglesRequest.
So is the response to a VNDetectFaceCaptureQualityRequest a superset of the properties returned by VNDetectFaceRectanglesRequest?
And is the expectation that VNDetectFaceCaptureQualityRequest uses more cycles and/or takes longer than VNDetectFaceRectanglesRequest?
I am hoping to be able to 'crop' a SwiftUI Image() and have the resulting (cropped) Image fill the space previously occupied by the Image() (aspectRatio differences not withstanding)
In the code below, commenting out the .clipShape modifier displays the original image, spanning the full width available.
When I add the .clipShape modifier, the resulting image only occupies its small portion of the original image. I want to stretch it to fill the available space, but don't know how I might do this.
Any/all guidance appreciate
struct ContentView: View {
var body: some View {
VStack() {
Image("Hana")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(CropFrame())
}
}
}
struct CropFrame: Shape {
func path(in rect: CGRect) -> Path {
return Path(CGRect(x: 17, y: 5, width: 73, height: 80))
}
}
My preference would have been to operate on a UIImage, however based on limitations outside the scope of this question, my most viable option is to operate on the SwiftUI Image()
thx again :-)
When I run the following code:
struct ContentView: View {
let mdText = LocalizedStringKey("some text [link](https://apple.com) more text")
let text = "some text link more text"
var body: some View {
VStack {
Text(mdText)
// .accessibilityRotor(.links, textRanges: [String.Index(utf16Offset: 10, in: text)..<String.Index(utf16Offset: 14, in: text)])
// .accessibilityIdentifier("identifier")
}
}
}
It works as expected. Specifically in voiceOver I'm able to use the rotor, select "Links" and select the link described in the markdown text.
However if I uncomment the .accessibilityIdentifier modifier, "Links" is no longer present in the Rotor.
To work around this, I would like to add an accessibilityRotor modifier to restore the Links Rotor item. When I uncomment the rotor modifier in the above code, Links is present in the Rotor, however there are no items in the list (ie swiping down just generates a 'klunk' noise)
I think I have two separate questions:
Is there any way to restore Links to the VO Rotor for a Text() using text Ranges?
Is there some way to add an accessibilityIdentifier that doesn't clobber the Links setting in the VO rotor?
thanks in advance :-)
Hello friends/colleagues,
I want to create a ViewModifier that accepts a conditional accessibilityIdentifier accepting an optional string input
struct AccessibilityModifier: ViewModifier {
let identifier: String?
func body(content: Content) -> some View {
if let identifier = identifier {
content
.accessibilityRepresentation {
content
.accessibilityIdentifier(identifier)
}
} else {
content
}
}
}
it mostly works as expected, but .popover appears to be broken.
For example, in the following code, the popover will work. But if I uncomment the .modifier line, the popover does not get presented
struct ContentView: View {
@State var isPresented: Bool = false
var body: some View {
VStack {
Button("Show Popover") {
isPresented = true
}
}
.popover(isPresented: $isPresented) {
Text("A Popover")
}
// .modifier(AccessibilityModifier(identifier: "a11y Modifier"))
}
}
The popover also works when I use:
.modifier(AccessibilityModifier(identifier: nil))
Any suggestions on how I can support both popovers and my conditional accessibilityIdentifier?
thanks, in advance,
Mike
I'm attempting to implement a star rating view using a drag gesture. (eg user can press on star#1, then 'change their mind' and drag to star 3 and release)
The code below will calculate the inferred star rating based on the width of the Stars HStack and the x value of the current drag position.
The odd behavior I'm seeing is when I drag off the leading (left) edge of the stars. sometimes the app gets non-responsive, repeatedly calling onChanged.
I believe the cause of this behavior is the following:
app detects the user drag has moved to a location to the left of the starts (eg -1, 15)
app updates rating Int to 0 and redraws the text view containing rating "0"
this increases the width of the root HStack, which causes the stars hstack to move slightly to the left
This effectively moves the drag position to just inside the start (eg 1, 15)
This changes the rating value to 1 which causes the text view value to update to "1"
This causes the stars hStack to move slightly to the right
This causes the drag position to update to be slightly to the left of the start (eg -1, 15)
goto 2
What's not clear to me is why this behavior continues even after the drag is completed.
There are many ways I've found to avoid/prevent this behavior including:
fixing the width of the Text containing the rating Int.
not letting the rating value be change to 0 when dragging to the left of the stars.
But these both feel like hacky work arounds.
Is there a better way to avoid this metastable ping pong behavior?
thanks!
struct ContentView: View {
@State var rating: Int = 0
var body: some View {
HStack {
GeometryReader { proxy in
HStack {
ForEach(0..<5) { index in
Image(systemName: symbolName(for: index))
}
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { gesture in
rating = starValue(for: gesture.location.x, width: proxy.size.width)
}
)
}
.frame(width: 145, height: 30)
Text("\(rating)")
}
}
func starValue(for xPosition: CGFloat, width: CGFloat) -> Int {
guard xPosition > 0 else {
return 0
}
let fraction = xPosition / width
let result = Int(ceil(fraction * 5))
return max(0, min(result, 5))
}
func symbolName(for index: Int) -> String {
if index < rating {
return "star.fill"
}
return "star"
}
}
When my parent container is disabled and I use VoiceOver, it reads out all the components in the container as "Dimmed" or "Disabled" (iOS 16.4 simulator says "Disabled" 16.6 actual device says "Dimmed", but I digress...)
I was hoping to be able to enable one of the components in the container, but VO continues to report the item as Disabled/Dimmed. (see code below)
Any suggestions on how I might be able to not report Text("Heading") as Dimmed/Disabled in VO?
struct ContentView: View {
@State var inputText: String = ""
var body: some View {
VStack {
Text("Heading")
.disabled(false)
TextField("Type some text", text: $inputText)
}
.disabled(true)
}
}
When I run the code below and enable VoiceOver on my iPhone running iOS 16.6, it navigates from "email1" to "email2".
When I run it on an iPhone simulator running iOS 16.4 and use the A11y Inspector it navigates from "email1" to the first textField, and then to "email2"
when I delete the .accessibilityRepresentation it navigates from "email1" to the first textField, and the to "email2" on both simulator and device.
Am I using .accessibilityRepresentation incorrectly? Or did something change between 16.4 and 16.6? Or is there a better question I should be asking?
any and all help greatly appreciated
struct ContentView: View {
@State var input1: String = ""
@State var input2: String = ""
var body: some View {
VStack {
TextInput(title: "email1", inputText: $input1)
TextInput(title: "email2", inputText: $input2)
}
}
}
struct TextInput: View {
let title: String
@Binding var inputText: String
init(title: String, inputText: Binding<String>) {
self.title = title
self._inputText = inputText
}
var body: some View {
VStack {
Text(title)
TextField("enter Text", text: $inputText)
.accessibilityRepresentation {
TextField("enter Text", text: $inputText)
}
}
}
}
I would have thought the following code would not compile.
Spoiler: It compiles, at least in Xcode 14.3.
Does this mean using a dot before an enum case name is just for show?
enum Stuff {
case first
case second
case third
static var allCases: [Stuff] {
[.first, .second, third]
}
}
I'm attempting to use an untappable UIBarButtonItem in a NavBar that would display a glyph and use CALayer to show a background shadow.
I have noticed that the item displays correctly at startup, however as soon as I push a new viewController the UIBarButtonItem clips. Do any of you know of a way to prevent the clipping that occurs after a push?
thanks, in advance!
At app launch
After push and pop
class ViewController: UIViewController {
var showBack: Bool
init(showBack: Bool = false) {
self.showBack = showBack
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
self.showBack = false
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = barButton
if !showBack {
navigationItem.leftBarButtonItem = barButton
}
}
var barButton: UIBarButtonItem {
let config = UIImage.SymbolConfiguration(pointSize: 30)
let view: UIImageView = .init(image: UIImage(systemName: "multiply.circle", withConfiguration: config))
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowRadius = 10
view.layer.shadowOffset = .zero
view.layer.shadowOpacity = 1.0
view.layer.masksToBounds = false
return .init(customView: view)
}
@IBAction func navPush(sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "vc") as! ViewController
vc.showBack = true
self.navigationController?.pushViewController(vc, animated: true)
}
}