SwiftUI - Add two gestures to a view? - swiftui

I want to add two gesture recongnisers to a view, but I'm unsure how to do it! I've tried the below, but only the first one fires (whatever order they are in).
.gesture(
TapGesture(count: 2)
.onEnded { _ in
print("Double Tap!")
}
)
.gesture(
LongPressGesture()
.onEnded { _ in
print("Long Press!")
}
)
I thing I need to use simultaneousGesture, but I'm unsure of the syntax. I've tried:
.simultaneousGesture(LongPressGesture().onEnded({self.showAddEditToDoView.toggle()}), TapGesture().onEnded({print("Double Tap!")}))
But that produces:
Missing argument label 'including:' in call
Any help much appreciated.

try
.simultaneousGesture(LongPressGesture().onEnded({ _ in
self.showAddEditToDoView.toggle()
}))
.simultaneousGesture(TapGesture().onEnded({
print("Double Tap!")
}))

Related

What is the best way to add ads to a SwiftUI Grid?

Hello I want to add ads to a swiftUI grid. The grid contains pictures that I get from a firebase backend and after every couple of pictures I would like to have an ad.
I am quite new to both SwiftUi and working with ads, so I'm not sure how correct my code is, but here is what I got so far.
// Code for the pictures Grid
struct PicturesGrid: View {
private let data: [Item]
var body: some View {
let gridItems = [GridItem(.fixed(UIScreen.screenWidth / 2),
alignment: .leading),
GridItem(.fixed(UIScreen.screenWidth / 2),
alignment: .leading)]
return ScrollView(showsIndicators: false) {
LazyVGrid(columns: gridItems) {
ForEach(0..<self.data.count, id: \.self) { index in
// Using this workaround for the ad to be on the whole width of the screen
// Also, after every six images I am adding and ad
if index != 0, index % 6 == 0 {
AdView()
.frame(width: UIScreen.screenWidth, height: 280)
.padding(.top, 20)
Spacer()
item
.frame(width: UIScreen.screenWidth / 2)
} else {
item
.frame(width: UIScreen.screenWidth / 2)
}
}
}
}
}
// this is for the picture
var item: some View {
NavigationLink(destination: DetailView(viewModel: DetailViewModel(item: itemAtIndexPath))) {
Cell(viewModel: CellViewModel(item: itemAtIndexPath))
}
.buttonStyle(PlainButtonStyle())
}
}
This is the code that I am currently using to load, create and display an ad
// Code for the ad that I am currently using
struct AdView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let adController = AdViewController(self)
return adController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
class AdViewController: UIViewController {
private var adView: AdView
/// The height constraint applied to the ad view, where necessary.
var heightConstraint: NSLayoutConstraint?
/// The ad loader. You must keep a strong reference to the GADAdLoader during the ad loading
/// process.
var adLoader: GADAdLoader!
/// The native ad view that is being presented.
var nativeAdView: GADUnifiedNativeAdView!
/// The ad unit ID.
let adUnitID = "ca-app-pub-3940256099942544/3986624511"
init(_ adView: AdView) {
self.adView = adView
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
var nibView: Any?
nibView = Bundle.main.loadNibNamed("ListAdView", owner: nil, options: nil)?.first
guard let nativeAdView = nibView as? GADUnifiedNativeAdView else {
return
}
setAdView(nativeAdView)
adLoader = GADAdLoader(adUnitID: adUnitID, rootViewController: self,
adTypes: [.unifiedNative], options: nil)
adLoader.delegate = self
DispatchQueue.global(qos: .background).async {
self.adLoader.load(GADRequest())
}
}
func setAdView(_ adView: GADUnifiedNativeAdView) {
// Remove the previous ad view.
DispatchQueue.main.async { [weak self] in
guard let weakSelf = self else {
return
}
weakSelf.nativeAdView = adView
weakSelf.view.addSubview(weakSelf.nativeAdView)
weakSelf.nativeAdView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for positioning the native ad view to stretch the entire width and height
let viewDictionary = ["_nativeAdView": weakSelf.nativeAdView!]
weakSelf.view.addConstraints(
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[_nativeAdView]|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
)
weakSelf.view.addConstraints(
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[_nativeAdView]|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
)
}
}
}
extension AdViewController: GADUnifiedNativeAdLoaderDelegate {
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error:
GADRequestError) {
print("didFailToReceiveAdWithError: \(error)")
}
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADUnifiedNativeAd) {
print("Received unified native ad: \(nativeAd)")
// Deactivate the height constraint that was set when the previous video ad loaded.
heightConstraint?.isActive = false
// Populate the native ad view with the native ad assets.
// The headline and mediaContent are guaranteed to be present in every native ad.
(nativeAdView.headlineView as? UILabel)?.text = nativeAd.headline
nativeAdView.mediaView?.mediaContent = nativeAd.mediaContent
// This app uses a fixed width for the GADMediaView and changes its height to match the aspect
// ratio of the media it displays.
if let mediaView = nativeAdView.mediaView, nativeAd.mediaContent.aspectRatio > 0 {
heightConstraint = NSLayoutConstraint(
item: mediaView,
attribute: .height,
relatedBy: .equal,
toItem: mediaView,
attribute: .width,
multiplier: CGFloat(1 / nativeAd.mediaContent.aspectRatio),
constant: 0)
heightConstraint?.isActive = true
}
// This asset is not guaranteed to be present. Check that it is before
// showing or hiding it.
(nativeAdView.advertiserView as? UILabel)?.text = nativeAd.advertiser
nativeAdView.advertiserView?.isHidden = nativeAd.advertiser == nil
// In order for the SDK to process touch events properly, user interaction should be disabled.
nativeAdView.callToActionView?.isUserInteractionEnabled = false
// Associate the native ad view with the native ad object. This is
// required to make the ad clickable.
// Note: this should always be done after populating the ad views.
nativeAdView.nativeAd = nativeAd
}
}
I want to mention that this is working at the moment, but the problems that I want to fix and I don't know how are:
The grid with the pictures load, but when I scroll over an ad, it takes several seconds for the ad to load and display. How could I at least hide it while it loads or make it faster?
If I scroll over an ad, the ad loads and if I continue scrolling, when I scroll back up, the ad is not loaded anymore and I have to wait for it to load again. How can I fix this? Or what is the best practice for this kind of scenario?
Should I use multipleAds? To load them before showing? If yes, then how should I do this?
Does what I am doing here look even a little bit correct? Please...I need some help
The Best Way to show ads in SwiftUI Grids is implementing Native Ads in your app to provide personalized ad experience

Is it possible to set a character limit on a TextField using SwiftUI?

[RESOLVED]
I am using a codable struct which stores the object values retrieved from an API call so I have amended my TextField using Cenk Belgin's example, I've also removed extra bits I've added in so if anyone else is trying to do the same thing then they won't have pieces of code from my app that aren't required.
TextField("Product Code", text: $item.ProdCode)
.onReceive(item.ProdCode.publisher.collect()) {
self.item.ProdCode = String($0.prefix(5))
}
Here is one way, not sure if it was mentioned in the other examples you gave:
#State var text = ""
var body: some View {
TextField("text", text: $text)
.onReceive(text.publisher.collect()) {
self.text = String($0.prefix(5))
}
}
The text.publisher will publish each character as it is typed. Collect them into an array and then just take the prefix.
From iOS 14 you can add onChange modifier to the TextField and use it like so :
TextField("Some Placeholder", text: self.$someValue)
.onChange(of: self.someValue, perform: { value in
if value.count > 10 {
self.someValue = String(value.prefix(10))
}
})
Works fine for me.
You can also do it in the Textfield binding directly:
TextField("Text", text: Binding(get: {item.ProCode}, set: {item.ProCode = $0.prefix(5).trimmingCharacters(in: .whitespacesAndNewlines)}))

Check state of Option-Key in SwiftUI (macOS)

I'm looking for a way to check the state of the option-key in SwiftUI on macOS.
I.e. depending on whether the option key is pressed or not I want to perform different actions in the .onTapGesture closure.
macOS-only SwiftUI has .modifiers modifier to specify EventModifiers, so your case is covered like in below example:
Rectangle()
.fill(Color.yellow)
.frame(width: 100, height: 40)
.gesture(TapGesture().modifiers(.option).onEnded {
print("Do anyting on OPTION+CLICK")
})
.onTapGesture {
print("Do anyting on CLICK")
}
While using the modifiers method on the gesture should probably be preferred, one can also actually test for the option key itself using CGEventSource in CoreGraphics:
import CoreGraphics
extension CGKeyCode
{
static let kVK_Option : CGKeyCode = 0x3A
static let kVK_RightOption: CGKeyCode = 0x3D
var isPressed: Bool {
CGEventSource.keyState(.combinedSessionState, key: self)
}
static var optionKeyPressed: Bool {
return Self.kVK_Option.isPressed || Self.kVK_RightOption.isPressed
}
}
This lets you detect the option key (or any other keys for that matter) in contexts where there isn't a modifier property or method.
The key codes in the extension can be renamed to be more Swifty, but those are the names that go way back to Classic MacOS's Toolbox and were defined in Inside Macintosh. I have a gist containing all the old key codes.
2022
.onTapGesture {
if NSEvent.modifierFlags.contains(.option) {
print("Option key Tap")
} else {
print("Tap")
}
}
.onDrag {
if NSEvent.modifierFlags.contains(.option) {
return NSItemProvider(object: "\(item.id)" as NSString)
}
return NSItemProvider()
}

SwiftUI PickerView with working CallBack HOW?

Ok -
I want a picker view to pick one operator: "=","<",">"
This operator will be sent as a binding:
#Binding var op:String
My Picker:
Picker(selection: binding, label: Text("Query Type")) {
ForEach(0..<self.operators.count) { index in
Text(self.operators[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
.padding()
Now My Binding with CallBack:
let binding = Binding<Int>(
get: {
return self.pickerSelection
},
set: {
//pickerSelection = $0
print("SETTTING: \($0)")
self.op = self.operators[self.pickerSelection]
self.queryCallback()
})
So, I can set the pickers perfectly. BUT, when I go back to edit my data, the picker never can choose the existing bound operator, say "<"
I put in the init an:
pickerSelection = operators.firstIndex(opValue)
However this will just start an infinite loop as pickerSelection is a #State variable
Anyone have a solution?
This is a method that works. It uses Combine to make an observable one can use to trigger the needed events. Also I see how useful Combine is with SwiftUI
https://stackoverflow.com/a/57519105/810300

How to create tappable url/phone number in SwiftUI

I would like to display a phone number in a SwiftUI Text (or any View), and then make it clickable so that it will open the 'Phone'.
Is there a way to do this with SwiftUI, or should I try to wrap a UITextView in SwiftUI and do it the old-fashioned way with NSAttributed string etc?
I've read the documentation for Text in SwiftUI, and couldn't find anything about how to do this. Currently trying to do this in Xcode 11 beta 5.
I've searched 'text' in the SwiftUI API in SwiftUI.h
I've also searched stackoverflow [swiftui] and google with queries like "make phone number/url tappable", "Tappable link/url swiftUI" etc..
Text("123-456-7890")
.onTapGesture {
// do something here
}
(text will be Japanese phone number)
Using iOS 14 / Xcode 12.0 beta 5
Use new link feature in SwiftUI for phone and email links.
// Link that will open Safari on iOS devices
Link("Apple", destination: URL(string: "https://www.apple.com")!)
// Clickable telphone number
Link("(800)555-1212", destination: URL(string: "tel:8005551212")!)
// Clickable Email Address
Link("apple#me.com", destination: URL(string: "mailto:apple#me.com")!)
Try this,
let strNumber = "123-456-7890"
Button(action: {
let tel = "tel://"
let formattedString = tel + strNumber
guard let url = URL(string: formattedString) else { return }
UIApplication.shared.open(url)
}) {
Text("123-456-7890")
}
Thanks to Ashish's answer, I found the necessary code I needed to solve this:
In the action inside of the Button - you need to call this method:
UIApplication.shared.open(url)
to actually make the phone call / open a link in a SwiftUI View.
Of course, I didn't understand how to format my phone number at first, which I found in these answers:
How to use openURL for making a phone call in Swift?
Don't forget to add the 'tel://' to the beginning of your string/format it as URL..
The full code of what worked is
Button(action: {
// validation of phone number not included
let dash = CharacterSet(charactersIn: "-")
let cleanString =
hotel.phoneNumber!.trimmingCharacters(in: dash)
let tel = "tel://"
var formattedString = tel + cleanString
let url: NSURL = URL(string: formattedString)! as NSURL
UIApplication.shared.open(url as URL)
}) {
Text(verbatim: hotel.phoneNumber!)
}
KISS answer:
Button("url") {UIApplication.shared.open(URL(string: "https://google.com")!)}
From iOS 14, Apple provides us a Link view by default. So, you can just use this,
Link("Anyone can learn Swift", destination: URL(string: "https://ohmyswift.com")!)
For the previous versions of iOS, like iOS 13.0, you still have to use
Button("Anyone can learn Swift") {
UIApplication.shared.open(URL(string: "https://ohmyswift.com")!)
}
iOS 15 (beta)
Take advantage of Markdown in SwiftUI, which supports links!
struct ContentView: View {
var body: some View {
Text("Call [123-456-7890](tel:1234567890)")
}
}
Result:
Make it a Button() with an action, not a Text() with a gesture.
Button {
var cleanNum = selectedItem.phoneNum
let charsToRemove: Set<Character> = [" ", "(", ")", "-"] // "+" can stay
cleanNum.removeAll(where: { charsToRemove.contains($0) })
guard let phoneURL = URL(string: "tel://\(cleanNum)") else { return }
UIApplication.shared.open(phoneURL, options: [:], completionHandler: nil)
} label: {
// ...
}
Using iOS 14 / Xcode 12.5
I add this in case you wants to use a view.
To make a call by tapping a View
Link(destination: URL(string: "tel:1234567890")!, label: {
SomeView()
})
To make a call by tapping a String
Link("1234567890", destination: URL(string: "tel:1234567890")!)
You can do this way as well if in case you need to log any event on tap of the link :
struct exampleView: View {
#SwiftUI.Environment(\.openURL) private var openURL
var body: some View {
Button {
if let url = URL(string: "https://www.google.com") {
openURL(url)
}
} label: {
Text("Link")
.foregroundColor(Color("EyrusDarkBlue"))
}
}
}