I thought that if the person posted a link within a description inside a textfield that I could make that link clickable. So, I first thought I would need to detect the link (which I have done) then make it clickable...I have tried several different things but still nothing. I have the link printing to my console--I just need the link to be clickable within the textfield.
You can add a UITapGestureRecognizer to your label:
let label = UILabel()
let clickGesture = UITapGestureRecognizer(target: self, action: #selector(click(_:)))
label.gestureRecognizers = [clickGesture]
func click(_ gestureRecognizer: UITapGestureRecognizer) {
//do stuff on label click
}
Related
I'm starting to learn swiftui and I've run into a problem that is both very basic and easily solvable in UIKit; but after spending days searching the internet and watching WWDC videos I've found no native solution.
The premise is simple: I have an array of songs I want to display in a list; when a user taps on a song view it should highlight the view on press, unhighlight after release, and then play the song (ie trigger an action). Sounds simple right?
Here's what I tried and spent way too much time on:
Using List(selection) + .onEvent(changed): I end up with a UUID (because i've only gotten selection to work with a UUID) that I then have to check against an array of songs to match AND the cell won't unhighlight/select itself; even when I try to manually set the State variable to nil or another generated UUID.
Using .onTap (either on or in the cell): I have to tap on the text of the cell to trigger onTap so I get a lot of taps that just don't work (because I have lots of white space in the cell). I also don't get a nice UI color change on press/release.
So after spending hours trying many different things I've finally come up with a solution and I basically wanted to create an account and share it to hopefully help other developers in my position. Because this so very annoyed me that something so basic took so much effort and time to do.
In the end the best solution I came up with was this:
Using ZStack and an empty button:
edit: I found I need to include and hide the content otherwise the button doesn't grow to fill the space (seems in lists it does for some reason). Though not sure what the hit on performance is of rendering the content twice when hiding it. Maybe a GeometryReader would work better?
struct SelectionView: ViewModifier {
let onSelect: () -> Void
func body(content: Content) -> some View {
ZStack (alignment: .leading) {
Button {
onSelect()
} label: {
content
.hidden()
}
content
}
}
}
extension View {
func onSelection(_ selection: #escaping () -> Void) -> some View {
self.modifier(SelectionView(onSelect: selection))
}
}
then to use it:
SongCell(song: song)
.onSelection {
// Do whatever action you want
}
No messing around with list selection, no weird tap hit boxes, and get the press/release color change. Basically put an empty button in a ZStack and trigger off it's action. Could possibly cause tap/touch issues with more complicated cells (?) but it does exactly what I need it to do for my basic app. I'm just not sure why it took so much effort and why apple doesn't support such a basic use case by default? If I've overlooked something native please do inform me. Thanks.
I got the basic idea what you are trying to do. I'm Going to show simple example. Maybe using this you will be able to find proper solution.
First let's create a color : -
#State var colorToShow : Color = Color.blue
Now in body we have our ZStack or Your cell that we want to deal with : -
ZStack{
colorToShow
}.frame(width: 50, height: 50).padding()
.onLongPressGesture(minimumDuration: 3) {
print("Process Complete")
colorToShow = .green
} onPressingChanged: { pressing in
if pressing {
print("Pressing")
colorToShow = .red
} else {
print("Pressing Released")
colorToShow = .blue
}
}
Here we are using .onLongPressGesture. You can set minimum duration on which you want to perform action. Now on process completion You set what you want to do. OnPressingChange give you a bool value that changes according to user is pressing that button or not. Show color change(Highlight) or do action while bool value is true. When user release button do action or unhighlight since bool value turns false.
Hope you find it useful.
During Xcode UI test, I found my custom view's (MenuViewButton) is un-hittable in a test, I could found it but cannot touch it. In debug , when I po isHittable in console, it returns false. However I'm not sure if this is the correct behavior.
Per this thread XCUIElement exists, but is not hittable said, isHittable is default false for custom view element, and default true for UIKit standard view. But I don't know if it is the same behavior in SwiftUI.
Since the way someView.isAccessibilityElement = true is not possible in SwiftUI. My question is how could I let my custom view became hittable? Then it could be tapped in a test.
private var aView: some View {
MenuViewButton(
image: Image("an image name"),
text: Text("a string")
)
.accessibility(identifier: "xxx name")
}
I also use force tap with coordinate in case tap() is not working, but to give a offset of normalizedOffset didn't fix the problem in all places, it means some un-hittable element could be tapped, that is great but some others still not.
So may I know where normalizedOffset is start, from the middle of frame to give the offset or the top left?
func forceTapElement(timeout: TimeInterval) {
if !self.waitForExistence(timeout: timeout) {
return
}
if self.isHittable {
self.tap()
} else {
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.0))
coordinate.tap()
}
}
Add https://github.com/devexperts/screenobject as a dependency
Use .tapUnhittable() instead of .tap() for this particular view. It gets the coordinates and taps using them
I have the same situation and am looking for answers. What has worked for me was to use:
let coordinate = element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
Still this seems like workaround for a real problem. One other strange thing is that i do not have this problem on iOS15.0 simulator, only for later versions. Currently trying with iOS15.2
One more thing I've tried is to add
.accessibilityElement(children: .combine)
and specificly telling it's a button with
.accessibility(addTraits: .isButton)
But this doesn't solve the problem.
Seems that isAccessibilityElement would be an answer here, but SwiftUI doesn't seem to have such.
Is there a way to change the output format of a Text using init(_ date: Date, style: Text.DateStyle)?
Using a .timer, the output is like: 0:42, but I want something like 00:00:42.
Background
I want to create a widget (iOS 14) where a timer is running, and as I think it's not a good idea to trigger a widget update every second, and this may even also not work reliably, at least that's not how widget are indented to be used.
So I thought about using this predefined timer functionality of Text.
I'm quite new to SwiftUI and don't really know about all the capabilities yet. So maybe I could create a similar custom Text-View by myself? Any ideas on how to achieve this?
Or asked differently: Is there a way to create such self-updating components by oneself, that also work in an iOS 14 widget? (Seems like using Timer.publish to update the View does not work in an iOS 14 widget)
No solution => Workaround
As of today, there doesn't seem to be a proper solution for this. But as the interest in a "solution" or workaround for this seems to be there, I'll just post what I ended up with, for now.
Code
Basically I just manually update the Text once a second, and calculate the difference from the reference date to "now".
struct SomeView: View {
let referenceDate = Date() // <- Put your start date here
#State var duration: Int = 0
let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
var body: some View {
Text(self.duration.formatted(allowedUnits: [.hour, .minute, .second]) ?? "")
.onReceive(self.timer) { _ in
self.duration = referenceDate.durationToNow ?? 0
}
}
}
extension Date {
var durationToNow: Int? {
return Calendar.current
.dateComponents([.second], from: self, to: Date())
.second
}
}
extension Int {
func formatted(allowedUnits: NSCalendar.Unit = [.hour, .minute]) -> String? {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = allowedUnits
formatter.zeroFormattingBehavior = .pad
return formatter.string(from: DateComponents(second: self))
}
}
WidgetKit limitations
This unfortunately does not work for WidgetKit, as the timer does not run or at least the UI does not refresh. So I ended up, only displaying minutes in the widget (which is kinda ok for my purpose) and just set an update in the "timeline" for each minute.
I have implemented code as per the google SDK documentation line by line in my app, but still when I click on google sign in button app shifts to new view controller with webview with blank screen. Have tried multiple solution found here : GIDSignIn white screen on iOS 91. But no lucks with resolving the problem , have attached the screen shot for getting closer look about the screen.
Following are the pods that I'm using,
Running XCode 9.1, iOS 10.0 and later. Kindly request someone to help.
Update: View Hierarchy
Update: viewDidLoad's code:
GIDSignIn.sharedInstance().uiDelegate = self
if self.isChangePassword {
self.addSignInView()
}
else {
self.addSignUpView()
}
fileprivate func addSignInView() {
guard let signInEmailView: SignInEmailView = Bundle.main.loadNibNamed(NibNames.SignInEmailView.rawValue, owner: self, options: nil)?[0] as? SignInEmailView
else {
return
}
signInEmailView.delegate = self
gaManager.trackScreen(screenName: ScreenNames.SignIn.rawValue)
self.animateView(signInEmailView)
}
fileprivate func addSignInView() {
guard let signInEmailView: SignInEmailView = Bundle.main.loadNibNamed(NibNames.SignInEmailView.rawValue, owner: self, options: nil)?[0] as? SignInEmailView
else {
return
}
signInEmailView.delegate = self
gaManager.trackScreen(screenName: ScreenNames.SignIn.rawValue)
self.animateView(signInEmailView)
}
I have the same problem. I use the UIAlertView to confirm user really want to do authorization. It will show the blank screen. If I remove the UIAlertView and show the authorization view directly. It works fine.
The problem also show in the Dropbox authorization screen.
If you not use UIAlertView , please try to pass the top most controller
https://github.com/dropbox/dropbox-sdk-obj-c/issues/182
Hope this can do some help.
Finally after so many days found the problem. I was showing splash view from my appDelegate using refrence of UIWindow which was adding and removing an imgview as subview to the window. So it was somehow messing with the UINavigation's stack and my view controller was not getting any reference to the UINavigationController. After removing that code it's working just fine. Also came to the solution that, if I want to show splash screen, as I can't use UIWindow's reference, I have to add new VC and write my all navigation code there. #TedYu Thank you very much for the help. :)
I am new to swift. I am trying to create a view where you can create hashtags. There is a UITextfield in which you type the word to be converted. On press of 'enter' or '#', it should automatically convert to hashtags and display in labels which are further stored in an array format.
I tried many tutorials but none of them seem to work.
UITextField's have a delegate that is pretty handy.
A really simple implementation would be to use the textFieldShouldReturn delegate method, you can use this to detect when the return button is pressed.
Tell your view controller that it is going to adopt the protocol like this:
class ViewController:UIViewController, UITextFieldDelegate {
...
Then tell your textfield where to delegate it's events, if you are making the textfield inside the view controller then use self to reference the view controller like this:
let textField = UITextField()
textField.delegate = self // IMPORTANT
self.view.addSubView(textField)
Then inside your view controller implement the textFieldShouldReturn method like so:
class ViewController:UIViewController, UITextFieldDelegate {
...
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let text = textField.text {
let hashtag = "#\(text)"
print("New hashtag is \(hashtag)")
}
return true // allows the default behaviour of return button, you can return false if you want the keyboard to remain on screen.
}
}
This solution does not account for the user entering more than one hashtag, nor does it remove white space etc.. you may need to think about formatting/validating this string before it is useable.
You may also want to consider looking at other existing questions that cover things like splitting strings and creating arrays.