Post

Replies

Boosts

Views

Activity

Can't combine UIImageView rotation animation and tableView section reload
I have 4 sections, each section have 2 nested rows. I open the rows by tapping on each section. Here is how my initial data looks like. It has title, subtitle and options (which is what nested rows should display): private var sections = [ SortingSection(title: "По имени", subtitle: "Российский рубль", options: ["По возрастанию (А→Я)", "По убыванию (Я→А)"]), SortingSection(title: "По короткому имени", subtitle: "RUB", options: ["По возрастанию (А→Я)", "По убыванию (Я→А)"]), SortingSection(title: "По значению", subtitle: "86,22", options: ["По возрастанию (1→2)", "По убыванию (2→1)"]), SortingSection(title: "Своя", subtitle: "в любом порядке", options: ["Включить"]) ] When I tap on a section I want it accessory (chevron.right, made as UIImageView) be rotated in sync with expanding of nested rows and when I click again the same behaviour for closing. I have a variable called isOpened (bool, false by default), which I change from false to true and back each tap in didSelectRowAt. Based on that a show all nested cells and rotate the UIImageView: override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 0 { sections[indexPath.section].isOpened.toggle() guard let cell = tableView.cellForRow(at: indexPath) as? MainSortTableViewCell else { return } UIView.animate(withDuration: 0.3) { if self.sections[indexPath.section].isOpened { cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2) } else { cell.chevronImage.transform = .identity } } completion: { _ in tableView.reloadSections([indexPath.section], with: .none) } } As you can see above I reload tableView section to show\hide nested rows in a completion block after animation. I can't use reloadSections in an if\else statement because then chevron animation gets skipped. Also my numberOrRowsInSection method: override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let section = sections[section] if section.isOpened { return section.options.count + 1 } else { return 1 } } Here is how it looks now: https://cln.sh/3hjI2q Here is what I want (any iPhone native apps): https://cln.sh/z0LM6E I tried to add and delete rows instead of reloading the whole section, but always end up with error: UIView.animate(withDuration: 0.3) { if self.sections[indexPath.section].isOpened { cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2) for i in 0..<self.sections[indexPath.section].options.count { tableView.insertRows(at: [IndexPath(row: 1+i, section: indexPath.section)], with: .none) } } else { cell.chevronImage.transform = .identity for i in 0..<self.sections[indexPath.section].options.count { tableView.deleteRows(at: [IndexPath(row: i-1, section: indexPath.section)], with: .none) } } } How can I change my code to solve the task and animate chevron at the same time nested rows expand or close?
0
0
511
Apr ’22
Fix a CollectionView item animation while changing a device orientation
I have made an Onboarding View using a collectionView with 2 items. When I change device orientation from landscape to portrait the second item being animated and visible within the first item screen borders. You can see the problem here: https://cln.sh/sgypvH How to fix the animation so when switching from landscape to portrait I could see only the first item content with a white background on the screen? Here is my code for collectionViewLayout: func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: collectionView.frame.width, height: collectionView.frame.height) } Here is the code for willTransition:     override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {         collectionView.collectionViewLayout.invalidateLayout()           let indexPath = IndexPath(item: self.currentPage, section: 0)         DispatchQueue.main.async {             self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)         }     } Please let me know if you need anything extra to help me to solve the issue. Thank you!
0
0
456
Apr ’22
How to avoid a double call of scrollViewDidScroll() method while swiping the collectionView?
I have created a User Onboarding as a  Collection View with 5 cells (pages). The Collection View has a UIPageControl which shows an active page user currently on and 2 UIButtons (previous and next) which needed to manually scroll the pages if user don't want to swipe. Here is how I manage the buttons IBAction when user tap: @IBAction func prevButtonClicked(_ sender: UIButton) { if currentPage != 0 { currentPage -= 1 let indexPath = IndexPath(item: currentPage, section: 0) collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) } } @IBAction func nextButtonClicked(_ sender: UIButton) { if currentPage == slides.count - 1 { //hide onboarding } else { currentPage += 1 let indexPath = IndexPath(item: currentPage, section: 0) collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) } } Also if user swipes a page instead of tap on buttons I use scrollViewDidScroll() method to update UIPageControl dot: func scrollViewDidScroll(_ scrollView: UIScrollView) { let visibleRectangle = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) let visiblePoint = CGPoint(x: visibleRectangle.midX, y: visibleRectangle.midY) currentPage = collectionView.indexPathForItem(at: visiblePoint)?.row ?? 0 } The currentPage is a computed property: private var currentPage = 0 { didSet { pageControl.currentPage = currentPage currentPage == 0 ? hidePreviousButton() : showPreviousButton() } } I have a problem: when tap on buttons I force collectionView to scroll and update currentPage, therefore scrollViewDidScroll called and currentPage updates again. Because of that when I tap on buttons I can see that UIPageControl dot and backButton are flicker since the code runs twice: didSet { pageControl.currentPage = currentPage currentPage == 0 ? hidePreviousButton() : showPreviousButton() } Here is a GIF with the problem: https://cln.sh/ffXG9A How can I avoid the double call to scrollViewDidScroll when tap on buttons?
0
0
1.3k
Apr ’22
How to reload collectionView cell when it did end displaying?
I have created a User Onboarding as a Collection View with 5 cells (pages). I have 2 types of Collection View Items: welcomeCell which is always a first and single cell in Onboarding (indexPath.item == 0) and tutorialCell (all other cells) which has a tableView and can scroll its content vertically. I want to add the next behaviour: user scrolls tableView content and swipes to the next page if user swipes back to the page he scrolled I want the page layout to be reloaded like it was initially set. For now if a user swipes to the page he scrolled, he will see its position where he ended the scroll. I suggests that collectionView content can be reloaded in didEndDisplaying method: func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if indexPath.item != 0 { self.collectionView.reloadItems(at: [indexPath]) } } But with this code I receive a strange cells behaviour: I can swipe up tableView on one page and the came content position will be on another page, but some page can be loaded with normal content position. Here is a GIF: https://cln.sh/wgnjS8 I also tried collectionView.reloadData() in scrollViewDidScroll and scrollViewDidEndDecelerating but receive the similar behaviour. Would you be so kind to share some experience and help to understand what's the best practice for my case? Maybe I should find a way to not reload a page content at all, but reload imageView height anchor in constraints?
0
0
1.9k
Apr ’22
How to check if one of URLSession tasks returned an error and if so to stop code execution?
I need to make 2 API calls simultaneously. I have 2 URLs for the calls, and if one of the calls will return any error I want to stop all the code execution. How I tried to do it: I have a function called performRequest() with a completion block. I call the function in my ViewController to update the UI - show an error/or a new data if all was successful. Inside it I create a URLSession tasks and then parse JSON: I created an array with 2 urls: func performRequest(_ completion: @escaping (Int?) -> Void) { var urlArray = [URL]() guard let urlOne = URL(string: "https://api.exchangerate.host/latest?base=EUR&places=9&v=1") else { return } guard let urlTwo = URL(string: "https://api.exchangerate.host/2022-05-21?base=EUR&places=9") else { return } urlArray.append(urlOne) urlArray.append(urlTwo) } Then for each of the url inside the array I create a session and a task: urlArray.forEach { url in let session = URLSession(configuration: .ephemeral) let task = session.dataTask(with: url) { data, _, error in if error != nil { guard let error = error as NSError? else { return } completion(error.code) return } if let data = data { let printData = String(data: data, encoding: String.Encoding.utf8) print(printData!) DispatchQueue.main.async { self.parseJSON(with: data) } } } task.resume() } print("all completed") completion(nil) } For now I receive print("all completed") printed once in any situation: if both tasks were ok, if one of them was ok or none of them. What I want is to show the print statement only if all tasks were completed successfully and to stop executing the code if one of them returned with error (for example if we will just delete one of the symbols in url string which will take it impossible to receive a data). How can I do it correctly?
0
0
424
May ’22
How to show a Large Title when scroll view up?
I have implemented a feature, when you press on a UITabBar icon and viewController1 scrolls up using its UIScrollView. It works perfectly, but if I scroll view down and stop somewhere, then switch to another viewController2, then get back to viewController1 and press tabBar icon - the viewController1 will scroll up, but Large Title will never be showed, and I should press tabBar icon one more time to show it: Here is a GIF with behaviour: https://i.stack.imgur.com/VfP4o.gif The code I use for scroll up the VC1: private var biggestTopSafeAreaInset: CGFloat = 0 override func viewSafeAreaInsetsDidChange() { super.viewSafeAreaInsetsDidChange() self.biggestTopSafeAreaInset = max(view.safeAreaInsets.top, biggestTopSafeAreaInset) } func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { if tabBarController.selectedIndex == 0 { let navigationVC = viewController as? UINavigationController let firstVC = navigationVC?.viewControllers.first as? CurrencyViewController guard let scrollView = firstVC?.view.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView else { return } if traitCollection.verticalSizeClass == .compact { scrollView.setContentOffset(CGPoint(x: 0, y: -view.safeAreaInsets.top, animated: true) } else { scrollView.setContentOffset(CGPoint(x: 0, y: -biggestTopSafeAreaInset, animated: true) } } } I tried to track biggestTopSafeAreaInset in different stages of VC1 life, but it always has the same number - 196.0. But then why it doesn't scroll till the Large Title after viewControllers switch?
0
0
535
Jun ’22
How to format numbers in UITextField after input from custom keyboard view?
My app used to rely on standard iOS Numpad Keyboard as a textField.inputView. The textField could group typed numbers (1000 -> 1 000, 50067 -> 50 067 etc) and limit the amount of numbers after the decimal point as 2 max: https://i.stack.imgur.com/69LVr.gif (GIF) I've created a custom numpad keyboard view and implement its logic with UITextInput protocol according to that guide on StackOverflow. I can't understand of how to deal with the code I used for grouping and limit numbers with standard iOS Numpad. Because it doesn't work properly after I type numbers from my new custom Numpad (only 2 numbers): https://i.stack.imgur.com/f3u3n.gif (GIF) The code I use for grouping and limit numbers after decimal point in a textField: func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.groupingSeparator = " " formatter.maximumFractionDigits = 2 let textString = textField.text ?? "" guard let range = Range(range, in: string) else { return false } let updatedString = textString.replacingCharacters(in: range, with: string) let correctDecimalString = updatedString.replacingOccurrences(of: ",", with: ".") let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "") guard completeString.count <= 12 else { return false } guard !completeString.isEmpty else { return true } textField.text = completeString return string == formatter.decimalSeparator } Custom NumpadView: class NumpadView: UIView { var target: UITextInput? init(target: UITextInput) { super.init(frame: .zero) self.target = target initializeSubview() } required init?(coder: NSCoder) { super.init(coder: coder) initializeSubview() } func initializeSubview() { let xibFileName = "NumpadView" let view = Bundle.main.loadNibNamed(xibFileName, owner: self, options: nil)![0] as! UIView self.addSubview(view) view.frame = self.bounds self.autoresizingMask = [.flexibleWidth, .flexibleHeight] } @IBAction func buttonPressed(_ sender: NumpadButton) { insertText((sender.titleLabel!.text)!) } func insertText(_ string: String) { guard let range = target?.selectedRange else { return } if let textField = target as? UITextField, textField.delegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) == false { return } target?.insertText(string) } } extension UITextInput { var selectedRange: NSRange? { guard let selectedRange = selectedTextRange else { return nil } let location = offset(from: beginningOfDocument, to: selectedRange.start) let length = offset(from: selectedRange.start, to: selectedRange.end) return NSRange(location: location, length: length) } } 2.How I initialize it in a VC: override func viewDidLoad() { super.viewDidLoad() textField.delegate = self textField.inputView = NumpadView(target: textField) } Test project on GitHub: CLICK What should I do so the code in shouldChangeCharactersIn method work for my custom Numpad?
0
0
532
Jul ’22
How to insert a character at any cursor position in a textField?
I have a textField where I can type numbers (2 333,44) or simple math equations (12+3, 11x3,5). For now I can modify the textField.text with adding a new string only to the end of existing text. If I move cursor to any position inside number it will add a new character only at the end: https://i.stack.imgur.com/HzaA9.gif How to modify the code to insert new characters at any cursor position? My current code from shouldChangeCharactersIn: func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.maximumFractionDigits = 2 formatter.minimumFractionDigits = 1 formatter.locale = .current formatter.roundingMode = .down let numberString = "\(textField.text ?? "")".replacingOccurrences(of: formatter.groupingSeparator, with: "") let lastCharacter = numberString.last ?? "." var symbol: String { var tempArray = [String]() let mathSymbols = "+-÷x" for character in numberString { if mathSymbols.contains(character) { tempArray.append(String(character)) } } return tempArray.last ?? "" } var numbersArray: [String] { if numberString.first == "-" { let positiveString = numberString.dropFirst() var tempArray = positiveString.components(separatedBy: symbol) tempArray[0] = "-\(tempArray[0])" return tempArray } else { return numberString.components(separatedBy: symbol) } } //turn numbers into Float and create a String from them to be able to receive a correct result from NSExpression var floatNumberArray: [Float] { if numberString.first == "-" { let tempString = lastCharacter == Character(formatter.decimalSeparator) ? numberString.dropFirst().dropLast() : numberString.dropFirst() var tempArray = tempString.components(separatedBy: symbol) tempArray[0] = "-\(tempArray[0])" return tempArray.compactMap { Float($0.replacingOccurrences(of: formatter.decimalSeparator, with: ".")) } } else { return numberString.components(separatedBy: symbol).compactMap { Float($0.replacingOccurrences(of: formatter.decimalSeparator, with: ".")) } } } var floatNumberString: String { if numberString.contains("x") { return "\(floatNumberArray.first ?? 0)\("*")\(floatNumberArray.last ?? 0)" } else if numberString.contains("÷") { return "\(floatNumberArray.first ?? 0)\("/")\(floatNumberArray.last ?? 0)" } else { return "\(floatNumberArray.first ?? 0)\(symbol)\(floatNumberArray.last ?? 0)" } } let amountOfDecimals = "\(numbersArray.last ?? "")\(string)".filter({ $0 == Character(formatter.decimalSeparator) }).count //allow to insert 0 after the decimal symbol to avoid formatting i.e. 2.04 formatter.minimumFractionDigits = numberString.last == Character(formatter.decimalSeparator) && string == "0" ? 1 : 0 //allow string to be modified by backspace button if string == "" { return false } //allow numbers as a first character, except math symbols if numberString == "" { if Character(string).isNumber { textField.text = string } else { return false } } //allow only one decimal symbol per number else if amountOfDecimals > 1 { return false } if numbersArray.count > 1 { //if number is entered if Character(string).isNumber { textField.text = "\(formatter.string(for: Double("\(numbersArray.first?.replacingOccurrences(of: formatter.decimalSeparator, with: ".") ?? "")") ?? 0) ?? "")\(symbol)\(formatter.string(for: Double("\(numbersArray.last?.replacingOccurrences(of: formatter.decimalSeparator, with: ".") ?? "")\(string)") ?? 0) ?? "")" //if symbol is entered } else if string == formatter.decimalSeparator { textField.text = "\(textField.text ?? "")\(string)" } else { //perform calculation if last entered character is a number if lastCharacter.isNumber { let result = performCalculation(format: floatNumberString) textField.text = string == "=" ? "\(formatter.string(from: result) ?? "")" : "\(formatter.string(from: result) ?? "")\(string)" //perform calculation if last entered character is a decimal symbol } else if lastCharacter == Character(formatter.decimalSeparator) { let result = performCalculation(format: floatNumberString) textField.text = string == "=" ? "\(formatter.string(from: result) ?? "")" : "\(formatter.string(from: result) ?? "")\(string)" //change math symbol before enter a second number } else { textField.text = "\(textField.text?.dropLast() ?? "")\(string)" } } } else { //if number is entered if Character(string).isNumber { textField.text = "\(formatter.string(for: Double("\(numbersArray.first?.replacingOccurrences(of: formatter.decimalSeparator, with: ".") ?? "")\(string)") ?? 0) ?? "")" } else { //if math symbol is entered if lastCharacter.isNumber { textField.text = "\(textField.text ?? "")\(string)" } } } return false } func performCalculation(format: String) -> NSNumber { let expression = NSExpression(format: format) let answer = expression.expressionValue(with: nil, context: nil) return answer as! NSNumber } } My guess is to modify the string with a range. So I tried: 1.Create range let numberString = "\(textField.text ?? "")".replacingOccurrences(of: formatter.groupingSeparator, with: "") guard let range = Range(range, in: numberString) else { return false } 2.Remove string at the end and use range instead } else { //if number is entered if Character(string).isNumber { textField.text = "\(formatter.string(for: Double("\(numbersArray.first?.replacingOccurrences(of: formatter.decimalSeparator, with: ".").replacingCharacters(in: range, with: string) ?? "") ?? "")") ?? 0) ?? "")" } else { //if math symbol is entered if lastCharacter.isNumber { textField.text = "\(textField.text?.replacingCharacters(in: range, with: string) ?? "") ?? "")" } } } return false } 3.Then I can insert at any position but can add only 4 numbers if add from keyboard and insert more from another cursor position: https://i.stack.imgur.com/iDV4n.gif Test project on GitHub
0
0
1.3k
Jul ’22
How to properly refactor repeated code of 2 ViewControllers?
I have 1 UIViewController with a type of CustomTableView and 1 UITableViewController with a type of usual UITableView. Both conform to NSFetchedResultsControllerDelegate and implemented its delegate methods with the repeated code. For now it's in extensions. Is it possible to move that code out to a separate swift file? I tried to move it to separate file with class NSFetchedResultsView, but when I copy that delegate methods to the new file, it doesn't know anything about tableView inside it's methods... How can I separate that methods properly? Delegate methods I want to separate: func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { tableView.beginUpdates() } func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { tableView.endUpdates() } func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { switch type { case .update: if let indexPath = indexPath { tableView.reloadRows(at: [indexPath], with: .none) } case .move: if let indexPath = indexPath, let newIndexPath = newIndexPath { tableView.moveRow(at: indexPath, to: newIndexPath) } case .delete: if let indexPath = indexPath { tableView.deleteRows(at: [indexPath], with: .none) } case .insert: if let newIndexPath = newIndexPath { tableView.insertRows(at: [newIndexPath], with: .none) } default: tableView.reloadData() } }
1
0
468
Jan ’22
How to correctly pass NSFetchedResultsController's numberOfObjects from one VC to another?
I have a VC1 with a UITableView which populate it cells with a NSFetchedResultsController. I want VC1 tableView to place all new added cells to the bottom of tableView, therefore I created a CoreData attribute: itemNumberForVC1(type Int32). The cells can be added only from VC2. That's why I need to pass the numberOfObjects from VC1 method numberOfRowsInSection to VC2. The numberOfObjects is in the following method of VC1: override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let numberOfObjects = fetchedResultsController.sections?[section].numberOfObjects else {return 0} return numberOfObjects } I need to use the numberOfObjects in my VC2 didSelectRowAt method: override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let currency = fetchedResultsController.object(at: indexPath) currency.isForConverter = !currency.isForConverter //currency.itemNumberForVC1 = numberOfObjects - 1 <- calculation: there I should receive the actual //number of objects from VC1 and assign the number to picked currency, and it should appear in VC1 //at the bottom of its TableView coreDataManager.save() } I tried to use delegates but inside VC2 didSelectRowAt the result is always 0. The max result I've got is created a method in VC2, which takes numberOfObjects as a parameter, call it in VC1 and then I have that number inside the VC2 but only in borders of the method. I can't move it to VC2 didSelectRowAt (tried through a global variable). How can I do that efficiently?
1
0
481
Feb ’22
How to setup and fetched Strings Array in CoreData?
I have a method which need an Array of Strings for further calculations. For now I set up this array through UserDefaults, because it should store small (3-characters string) amount of data: func setRow(for currency: Currency, in currencies: [Currency]) { // initialise array from UserDefaults var currencyRowsArray = UserDefaults.standard.stringArray(forKey: "currencyRowsArray") ?? [String]() // calculations with the array (add or remove string object to\from it if currency.isForConverter { currencyRowsArray.append(currency.shortName!) } else { guard let row = currencyRowsArray.firstIndex(of: currency.shortName!) else { return } currencyRowsArray.remove(at: row) currency.rowForConverter = 0 } for (row, object) in currencyRowsArray.enumerated() { for currency in currencies { if object == currency.shortName { currency.rowForConverter = Int32(row) } } } // save array back to UserDefaults after calculations UserDefaults.standard.set(currencyRowsArray, forKey: "currencyRowsArray") } But since I have a CoreData implemented also I decided to store it in CoreData rather than UserDefaults. Here is how I tried to implement that: 1.I have entity Currency, so I created an attribute currencyRowsArray: Type - Transformable, Custom Class - [String], Transformer - NSSecureUnarchiveFromData, Optional - YES (because I don't need it to initialize every time a created a Currency object with other attributes) 2.In the setRow method: let request: NSFetchRequest<Currency> = Currency.fetchRequest() request.predicate = NSPredicate(format: "currencyRowsArray IN %@", currencyRowsArray) 3.Initialize the empty array to fill it later with fetched Data: currencyRowsArray = [String]() 4.Fetch the array: do { let fetchResult = try context.fetch(request) currencyRowsArray = fetchedResult as! [String] } catch { print(error) } 5.After calculations save the changes: do { try context.save() } catch { print(error) } Full changed code of the setRow method: func setRow(for currency: Currency, in currencies: [Currency]) { var currencyRowsArray = [String] () let request: NSFetchRequest<Currency> = Currency.fetchRequest() request.predicate = NSPredicate(format: "currencyRowsArray IN %@", currencyRowsArray) do { let fetchResult = try context.fetch(request) currencyRowsArray = fetchResult as! [String] } catch { print(error) } if currency.isForConverter { currencyRowsArray.append(currency.shortName!) } else { guard let row = currencyRowsArray.firstIndex(of: currency.shortName!) else { return } currencyRowsArray.remove(at: row) currency.rowForConverter = 0 } for (row, object) in currencyRowsArray.enumerated() { for currency in currencies { if object == currency.shortName { currency.rowForConverter = Int32(row) } } } do { try context.save() } catch { print(error) } } But every time the array loads as empty and when I make change, array doesn't add or remove items in it. What I did wrong?
1
0
821
Feb ’22
How to limit the amount of entered decimal characters in textField?
I have a textField where user can enter a number which can has a decimals. I want to restrict the amount of decimal characters possible to enter till 4. I'm trying to reach this behaviour with this code: let split = completeString.components(separatedBy: ".") if split.count == 2 { print(split.last!) if split.last!.count >= 4 { return false } } Full code for shouldChangeCharactersIn method: func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let formatter = converterManager.setupNumberFormatter() let textString = textField.text ?? "" guard let range = Range(range, in: textString) else { return false } let updatedString = textString.replacingCharacters(in: range, with: string) let correctDecimalString = updatedString.replacingOccurrences(of: ",", with: ".") let completeString = correctDecimalString.replacingOccurrences(of: formatter.groupingSeparator, with: "") let split = completeString.components(separatedBy: ".") if split.count == 2 { print(split.last!) if split.last!.count >= 4 { return false } } numberFromTextField = completeString.isEmpty ? 0 : Double(completeString) guard completeString.count <= 12 else { return false } guard !completeString.isEmpty else { return true } textField.text = formatter.string(for: numberFromTextField) return string == formatter.decimalSeparator } } This code successfully split the string, give me the numbers user enters after decimal sign, but: textField shows 3 character instead of 4 print shows that despite textField shows 3, when I press 4th digits in shows in the split array when after 4th digit I continue to press numbers it changes last digit. I want if there is a 4 numbers entered already allow only delete not change the last number GIF with behaviour: https://cln.sh/5vf1iq How to fix that behaviour?
1
0
1.3k
Feb ’22
Ambiguous Layout: Position and size are ambiguous for "Stack View"
I am building a Settings screen using Storyboard and UITableViewController with Static Cells. Here is a full view hierarchy and constraints I use for every cell in my Settings Table View: https://cln.sh/tAmFbl The problem appears when I switch portrait mode on landscape mode or switch to device with a smaller screen. Starting to receive an errors like: Ambiguous Layout: Position and size are ambiguous for "Stack View". And: Stack View, need constraints for: X position, width Stack View, need constraints for: Y position, height Here is a picture of the behaviour: https://cln.sh/fq6kOK If I switch back to portrait mode all errors goes away... How can I fix that?
1
0
3.3k
Feb ’22
UIGestureRecognizer works outside of UIView borders instead of inside
I've implemented a popup notification in my app to show user the data was being updated successfully or not. This popup is just a UIView instantiated from .xib. Here is screenshot of elements hierarchy in xib file: https://cln.sh/te8JhM Also I wanted the ability to swipe out the popup if user don't want to see a full time it shows itself on the screen. To do that I have implemented a UIGestureRecognizer: private func configureTapGesture() { guard let window = UIApplication.shared.windows.first(where: {$0.isKeyWindow}) else { return } let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:))) swipe.direction = .up window.addGestureRecognizer(swipe) } @objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) { if sender.direction == .up { UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) { self.popupView.center.y -= 50 } completion: { _ in self.popupView.removeFromSuperview() } self.isRemovedByTap = true } } When I run the app it works only if I swipe out anywhere but not inside the popupView. Please check for the GIF: https://cln.sh/sNm5RN If I replace a target from self (Popup class) to popupView (UIVisualEffectView, which is a rounded rectangle you see on GIF), then I receive an error unrecognized selector sent to instance Here is a my full custom Popup class where I initialize the view, configure, animate and show it: import UIKit class PopupView: UIView { @IBOutlet weak var popupView: UIVisualEffectView! @IBOutlet weak var symbol: UIImageView! @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var descriptionLabel: UILabel! private var isRemovedByTap = false override init(frame: CGRect) { super.init(frame: frame) configure() } required init?(coder: NSCoder) { super.init(coder: coder) configure() } private func configure() { if let views = Bundle.main.loadNibNamed("PopupView", owner: self) { guard let view = views.first as? UIView else { return } view.frame = bounds addSubview(view) } } private func configurePopup() { guard let window = UIApplication.shared.windows.first(where: {$0.isKeyWindow}) else { return } popupView.layer.cornerRadius = 20 popupView.clipsToBounds = true popupView.center.x = window.frame.midX popupView.translatesAutoresizingMaskIntoConstraints = true window.addSubview(popupView) } private func animatePopup() { UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) { self.popupView.center.y += 34 } completion: { _ in UIView.animate(withDuration: 0.15, delay: 10.0, options: .curveLinear) { self.popupView.center.y -= 50 } completion: { _ in if !self.isRemovedByTap { self.popupView.removeFromSuperview() } } } } private func configureTapGesture() { guard let window = UIApplication.shared.windows.first(where: {$0.isKeyWindow}) else { return } let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:))) swipe.direction = .up window.addGestureRecognizer(swipe) } @objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) { if sender.direction == .up { UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) { self.popupView.center.y -= 50 } completion: { _ in self.popupView.removeFromSuperview() } self.isRemovedByTap = true } } func showPopup(title: String, message: String, symbol: UIImage) { titleLabel.text = title descriptionLabel.text = message self.symbol.image = symbol configurePopup() animatePopup() configureTapGesture() } } What should I change to be able to swipe out only when inside the popupView rounded rectangle?
1
0
847
Mar ’22
Visual Effect View's Blur doesn't work on UIView from xib
I created a custom Numpad keyboard through xib and wanted to add a blur effect to its background. So I add a Visual Effect View in xib: https://i.stack.imgur.com/QjiwP.png Main View and Visual Effect View background color is set to Default and I also tried to use Clear Color. The problem is when I initialize the Numpad, background has a light grey color without any blur effect: https://i.stack.imgur.com/LebUA.png How to add a blur effect to the Numpad so yellow square can be blurred and visible? Code for NumpadView: import UIKit class NumpadView: UIView { @IBOutlet weak var resetButton: NumpadButton! @IBOutlet weak var decimalButton: NumpadButton! var target: UITextInput? var view: UIView? init(target: UITextInput, view: UIView) { super.init(frame: .zero) self.target = target self.view = view initializeSubview() } required init?(coder: NSCoder) { super.init(coder: coder) initializeSubview() } func initializeSubview() { let xibFileName = "NumpadView" let view = Bundle.main.loadNibNamed(xibFileName, owner: self, options: nil)![0] as! UIView self.addSubview(view) view.frame = self.bounds self.autoresizingMask = [.flexibleWidth, .flexibleHeight] } } Code for initializing in a VC: import UIKit class NumpadViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var textField: UITextField! override func viewDidLoad() { super.viewDidLoad() textField.delegate = self textField.inputView = NumpadView(target: textField, view: view) } }
1
0
826
Aug ’22