DeleteBackward() deletes only one character, is there any way to keep on deleting backwards ?
I am using emojiKeyboard and I have a delete emoticon. I detect the emoji being the delete emoticon and I call
if emoticon.isDelete{
deleteBackward()
return
}
Update:
Steven's solution works on buttons but not on my UITextView. Will try and find out why. I have tried having the addGestureRecognizer in ViewWillAppear as well as ViewDidLoad.
This should get you started, didn't test but should do the trick.
fileprivate var timer = Timer()
fileprivate var textField = UITextField() //change to your field
override func viewDidLoad() {
super.viewDidLoad()
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPress(_:)))
textField.addGestureRecognizer(longPress)
}
func longPress(_ guesture: UILongPressGestureRecognizer) {
if guesture.state == UIGestureRecognizerState.began {
longPressBegun(guesture)
} else if guesture.state == UIGestureRecognizerState.changed {
//longPressStateChanged(guesture)
} else if guesture.state == UIGestureRecognizerState.ended {
longPressEnded()
} else if guesture.state == UIGestureRecognizerState.cancelled {
longPressCancelled()
}
}
func longPressBegun(_ guesture: UILongPressGestureRecognizer) {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(repeatAction), userInfo: nil, repeats: true)
}
func longPressEnded() {
timer.invalidate()
}
func longPressCancelled() {
timer.invalidate()
}
func repeatAction() {
deleteBackward()
}
Related
I am trying to call model form #main App where the model has the dependency on a repository with init function. The repository has the URLSession and Baseurl properties . I have passed the required property on both approach ..
Here is approach I have tried based on Xcode suggestions ..
#main
struct HomwWorkWithSwiftUIApp: App {
#StateObject var model = FruitsModel(fruitRepository: FruitsRepository.self as! FruitsRepository)
var body: some Scene {
WindowGroup {
ContentView().environmentObject(model)
}
}
}
As a result as was crashed at run time with error Thread 1: signal SIGABRT
The second approach is passing the require parameters like this ..
#main
struct HomwWorkWithSwiftUIApp: App {
#StateObject var model = FruitsModel(fruitRepository: RealFruitsRepository(session: URLSession, baseURL: EndPoint.baseUrl))
var body: some Scene {
WindowGroup {
ContentView().environmentObject(model)
}
}
}
It giving error ..Cannot convert value of type 'URLSession.Type' to expected argument type 'URLSession'
Here is attempt for URLSession instance.
#main
struct HomwWorkWithSwiftUIApp: App {
init() {
}
var url : URLSession
init(url: URLSession) {
self.url = url
}
#StateObject var model = FruitsModel(fruitRepository: RealFruitsRepository(session: url, baseURL: EndPoint.baseUrl))
var body: some Scene {
WindowGroup {
ContentView().environmentObject(model)
}
}
}
Here is the screenshot ..
Here is the repository code ..
import Foundation
protocol FruitsRepository: WebRepository {
func loadFruits() async throws -> [Fruits]
}
struct RealFruitsRepository: FruitsRepository {
let session: URLSession
let baseURL: String
init(session: URLSession, baseURL: String) {
self.session = session
self.baseURL = baseURL
}
func loadFruits() async throws -> [Fruits] {
guard let request = try? API.allFruits.urlRequest(baseURL: baseURL) else {
throw APIError.invalidURL
}
guard let data = try? await call(request: request) else {
throw APIError.unexpectedResponse
}
guard let fruits = getDecodedFruitesResopnse(from: data) else {
throw APIError.unexpectedResponse
}
return fruits
}
private func getDecodedFruitesResopnse(from data: Data)-> [Fruits]? {
guard let fruites = try? JSONDecoder().decode([Fruits].self, from: data) else {
return nil
}
return fruites
}
}
extension RealFruitsRepository {
enum API {
case allFruits
case fruitDetails(Fruits)
}
}
extension RealFruitsRepository.API: APICall {
var path: String {
switch self {
case .allFruits:
return "/all"
case let .fruitDetails(fruit):
let encodedName = fruit.name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
return "/name/\(encodedName ?? fruit.name)"
}
}
var method: String {
switch self {
case .allFruits, .fruitDetails:
return "GET"
}
}
var headers: [String: String]? {
return ["Accept": "application/json"]
}
func body() throws -> Data? {
return nil
}
}
Here is the model class ..
import Foundation
import Combine
protocol FruitsModelInput {
func getFruits() async
}
protocol FruitsModelOutput {
var state: FruitViewStates { get }
var fruitRecordsCount: Int { get }
func getFruit(index: Int)-> Fruits
func getFruitsDetails(for row:Int)-> FruitsDetails
}
struct FruitsDetails {
let genus, name: String
}
final class FruitsModel: ObservableObject {
private var fruitsRepository: FruitsRepository
var fruits: [Fruits] = []
#Published var state: FruitViewStates = .none
private var cancellables:Set<AnyCancellable> = Set()
init(fruitRepository: FruitsRepository) {
self.fruitsRepository = fruitRepository
}
}
extension FruitsModel: FruitsModelOutput {
func getFruitsDetails(for row: Int) -> FruitsDetails {
if row >= 0 {
let fruit = fruits[row]
return FruitsDetails(genus: fruit.genus, name: fruit.name)
}
return FruitsDetails(genus: "", name: "")
}
var fruitRecordsCount: Int {
return fruits.count
}
func getFruit(index: Int) -> Fruits {
if fruits.count > 0 {
return (fruits[index])
} else {
return Fruits(genus: "", name: "", id: 0, family: "", order: "", nutritions: Nutritions(carbohydrates: 0.0, protein: 0.0, fat: 0.0, calories: 0, sugar: 0.0))
}
}
}
extension FruitsModel: FruitsModelInput {
func getFruits() async {
state = .showActivityIndicator
do {
fruits = try await fruitsRepository.loadFruits()
self.state = .showFruitList
} catch let error {
fruits = []
print(error)
state = .showError((error as! APIError).localizedDescription)
}
}
}
here is something that keeps me awake for three days already: I'm writing a little app that connects via BlueTooth to an Arduino. To get visual feedback about the connection state and the transmitted data, I use a view that allows me to connect/disconnect as well as shows me the state and data:
VStack {
Text("Glove Training App")
.font(.title)
HStack {
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange)
.font(.subheadline)
.padding(.bottom, 30)
Text(peripheral.transmittedString)
.font(.subheadline)
.padding(.bottom, 30)
}
}
In a separate file I have all the BT management:
class MyBluetoothManager: NSObject, ObservableObject {
#Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
(...)
func setConnected(peripheral: CBPeripheral) {
(...)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
class MyPeripheralDelegate: NSObject, ObservableObject, CBPeripheralDelegate {
let objectWillChange = ObservableObjectPublisher()
var transmittedString: String = "No data" {
willSet { objectWillChange.send()
}
}
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
(...)
let rxData = characteristic.value
if let str = NSString(data: rxData!, encoding: String.Encoding.utf8.rawValue) as String? {
print(str)
self.transmittedString = str
let measurement = str.components(separatedBy: "|")
(...)
} else {
print("not a valid UTF-8 sequence")
}
}
}
The values are initially set correctly, but then never updated. In the terminal I can see the printed values and the app works otherwise as expected. I'm on the latest version of XCode.
I looked at several tutorials, and this seems to be tricky. Any help would be highly appreciated.
Cheers,
Christian
EDIT: Here is the full BluetoothManager class (not my code mostly but works fine):
class MyBluetoothManager: NSObject, ObservableObject {
#Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
var state = State.poweredOff
enum State {
case poweredOff
case restoringConnectingPeripheral(CBPeripheral)
case restoringConnectedPeripheral(CBPeripheral)
case disconnected
case scanning(Countdown)
case connecting(CBPeripheral, Countdown)
case discoveringServices(CBPeripheral, Countdown)
case discoveringCharacteristics(CBPeripheral, Countdown)
case connected(CBPeripheral)
case outOfRange(CBPeripheral)
var peripheral: CBPeripheral? {
switch self {
case .poweredOff: return nil
case .restoringConnectingPeripheral(let p): return p
case .restoringConnectedPeripheral(let p): return p
case .disconnected: return nil
case .scanning: return nil
case .connecting(let p, _): return p
case .discoveringServices(let p, _): return p
case .discoveringCharacteristics(let p, _): return p
case .connected(let p): return p
case .outOfRange(let p): return p
}
}
}
func scan() {
guard central.state == .poweredOn else {
self.stateChange = "Cannot scan, BT is not powered on"
print("Cannot scan, BT is not powered on")
return
}
central.scanForPeripherals(withServices: [myDesiredServiceId], options: nil)
state = .scanning(Countdown(seconds: 10, closure: {
self.central.stopScan()
self.state = .disconnected
self.stateChange = "Scan timed out"
print("Scan timed out")
}))
}
func disconnect(forget: Bool = false) {
if let peripheral = state.peripheral {
central.cancelPeripheralConnection(peripheral)
}
if forget {
UserDefaults.standard.removeObject(forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
}
self.stateChange = "Disconnected"
state = .disconnected
}
func connect(peripheral: CBPeripheral) {
central.connect(peripheral, options: nil)
state = .connecting(peripheral, Countdown(seconds: 10, closure: {
self.central.cancelPeripheralConnection(peripheral)
self.state = .disconnected
self.stateChange = "Connect timed out"
print("Connect timed out")
}))
}
func discoverServices(peripheral: CBPeripheral) {
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverServices([myDesiredServiceId])
state = .discoveringServices(peripheral, Countdown(seconds: 10, closure: {
self.disconnect()
self.stateChange = "Could not discover services"
print("Could not discover services")
}))
}
func discoverCharacteristics(peripheral: CBPeripheral) {
guard let myDesiredService = peripheral.myDesiredService else {
self.disconnect()
return
}
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverCharacteristics([myDesiredCharacteristicId],
for: myDesiredService)
state = .discoveringCharacteristics(peripheral, Countdown(seconds: 10,
closure: {
self.disconnect()
self.stateChange = "Could not discover characteristics"
print("Could not discover characteristics")
}))
}
func setConnected(peripheral: CBPeripheral) {
guard let myDesiredCharacteristic = peripheral.myDesiredCharacteristic
else {
self.stateChange = "Missing characteristic"
print("Missing characteristic")
disconnect()
return
}
UserDefaults.standard.set(peripheral.identifier.uuidString,
forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.setNotifyValue(true, for: myDesiredCharacteristic)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange) << why don't you use MyBluetoothManager.shared here ? is there a second instance? this might be the error...but unfortunately you just showed us a small piece of code...
I've got a UImage, that needs to rotate until my call to the server ends.
But my image rotates only one time and stops after that.
My code:
#IBOutlet var imgLoader: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos: .userInitiated).async {
self.getSalons()
}
self.launchLoaderDesign()
NotificationCenter.default.addObserver(forName:NSNotification.Name("LoadAllShowNotification"), object: nil, queue: nil, using: notificationFinish)
}
func getSalons() -> Void {
let api:ApiSvain = ApiSvain()
api.getHeartStrokeSalon()
api.getSalonForHomePage()
}
func launchLoaderDesign() -> Void {
var posImg:Int = 0
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: { () -> Void in
self.imgLoader.transform = self.imgLoader.transform.rotated(by: CGFloat(M_PI_2))
})
{ (finished) -> Void in
self.launchLoaderDesign()
}
}
func getSalonForHomePage(){
let url = "MY_URL"
Alamofire.request(url, method: .get).validate().responseJSON
{ response in
if (response.error == nil)
{
let json = JSON(response.result.value!)
for (index, element) in json
{
let show:Show = Show(json: element, index: index)
StaticData.arrayOfShow.append(show)
}
NotificationCenter.default.post(name:NSNotification.Name("LoadAllShowNotification"), object: nil, userInfo: nil)
}
else
{
print(response.error!)
}
}
}
My function getSalonForHomePage sends a notification, and when I catch it I use performSegue to move to my new page.
I think my problem came from my misunderstanding of multi-threading.
Ps: I am using alamofire 4, for send request to my server.
How do I check if I have pressed on an NSSearchField search button?
search button of NSSearchField
#IBAction func searchTextField(_ sender: NSSearchField) {
if `searchButtonIsClicked` {
//Code if searchButtonIsClicked
return
}
if sender.stringValue != "" {
//My code
}
}
What I need to do instead of searchButtonIsClicked?
Here is the solution.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var searchFieldText: NSSearchField!
#IBOutlet weak var labelSearchText: NSTextField!
#IBAction func searchFieldButton(_ sender: NSSearchField) {
if searchFieldText.stringValue != "" {
//My code. For example:
labelSearchText.stringValue = searchFieldText.stringValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let cellMenu = NSMenu(title: "Search Menu")
var item: NSMenuItem!
item = NSMenuItem(title: "Clear", action: nil, keyEquivalent: "")
item.tag = Int(NSSearchFieldClearRecentsMenuItemTag)
cellMenu.insertItem(item, at: 0)
item = NSMenuItem.separator()
item.tag = Int(NSSearchFieldRecentsTitleMenuItemTag)
cellMenu.insertItem(item, at: 1)
item = NSMenuItem(title: "Recent Searches", action: nil, keyEquivalent: "")
item.tag = Int(NSSearchFieldRecentsTitleMenuItemTag)
cellMenu.insertItem(item, at: 2)
item = NSMenuItem(title: "Recents", action: nil, keyEquivalent: "")
item.tag = Int(NSSearchFieldRecentsMenuItemTag)
cellMenu.insertItem(item, at: 3)
searchFieldText.searchMenuTemplate = cellMenu
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Thank you for your help.
I think this demo can resolve your question.
Swift Code is below:
if let cell = searchField.cell as? NSSearchFieldCell {
let searchButtonCell: NSButtonCell = cell.searchButtonCell!
let cacelButtonCell: NSButtonCell = cell.cancelButtonCell!
searchButtonCell.target = self
cacelButtonCell.target = self
searchButtonCell.action = #selector(clickSearchButton(_:))
cacelButtonCell.action = #selector(clickCacelButton(_:))
}
I am trying to make a numbered list out of the information the user inputs into a UITextView. For example,
List item one
List item two
List item three
Here is the code that I have tried but does not give me the desired effect.
var currentLine: Int = 1
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Add "1" when the user starts typing into the text field
if (textView.text.isEmpty && !text.isEmpty) {
textView.text = "\(currentLine). "
currentLine += 1
}
else {
if text.isEmpty {
if textView.text.characters.count >= 4 {
let str = textView.text.substring(from:textView.text.index(textView.text.endIndex, offsetBy: -4))
if str.hasPrefix("\n") {
textView.text = String(textView.text.characters.dropLast(3))
currentLine -= 1
}
}
else if text.isEmpty && textView.text.characters.count == 3 {
textView.text = String(textView.text.characters.dropLast(3))
currentLine = 1
}
}
else {
let str = textView.text.substring(from:textView.text.index(textView.text.endIndex, offsetBy: -1))
if str == "\n" {
textView.text = "\(textView.text!)\(currentLine). "
currentLine += 1
}
}
}
return true
}
as suggested here: How to make auto numbering on UITextview when press return key in swift but had no success.
Any help is much appreciated.
You can subclass UITextView, override method willMove(toSuperview and add an observer for UITextViewTextDidChange with a selector that break up your text into lines enumerating and numbering it accordingly. Try like this:
class NumberedTextView: UITextView {
override func willMove(toSuperview newSuperview: UIView?) {
frame = newSuperview?.frame.insetBy(dx: 50, dy: 80) ?? frame
backgroundColor = .lightGray
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidChange), name: .UITextViewTextDidChange, object: nil)
}
func textViewDidChange(notification: Notification) {
var lines: [String] = []
for (index, line) in text.components(separatedBy: .newlines).enumerated() {
if !line.hasPrefix("\(index.advanced(by: 1))") &&
!line.trimmingCharacters(in: .whitespaces).isEmpty {
lines.append("\(index.advanced(by: 1)). " + line)
} else {
lines.append(line)
}
}
text = lines.joined(separator: "\n")
// this prevents two empty lines at the bottom
if text.hasSuffix("\n\n") {
text = String(text.characters.dropLast())
}
}
}
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let textView = NumberedTextView()
view.addSubview(textView)
}
}