How to pass currentPage rom UIScrollView to newViewController - ios

For the life of me I can't seem to figure this out. I must be completely overthinking this...
I have a UIScrollView that will hold a between 1-3 UIImageView(w UIImage) inside my tableView.
TableViewCell:
I'm calling...
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let currentPage = (CGFloat(imageScrollView.contentOffset.x) / CGFloat(userImageView!.frame.size.width))
pageControl.currentPage = Int(currentPage)
}
to find out what page the user is currently looking at. My question is how do I pass the currentPage number to my new ViewController?
TableViewController:
In my CellForRowAtIndex I'm calling to find out which button was clicked.
cell.tapToViewButton.tag = indexPath.row
cell.tapToViewButton.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
I'm using
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "toViewImageVC"){
let viewImageVC: ViewImageViewController = segue.destinationViewController as! ViewImageViewController
//passed [UIImage] correctly
//HOW TO PASS CURRENT PAGE????----
}
}
I've tried several things like using the UIScrollViewDelegate in my TableView, using creating a method in my tableViewCell that returns an Int() which is my current page but I can't access it through tableView cause that only loads the cell info.
This seems simple but for some reason I can't wrap my head around this!!!

In your ViewImageViewController add a page parameter, like:
var pageNumber = 0
Then in prepareForSegue call:
viewImageVC.pageNumber = pageControl.currentPage

Related

When trying to segue to a view controller from a table view i get this error: Unexpectedly found nil while unwrapping

I have a segue named "hydrogenSegue" from a "hydrogenBoxButton" to a "Hydrogen" view controller. However, I also wanted to implement a table view so I could search for an element. I tried to make the code so when the cell is clicked it will segue over to the element's view. I used hydrogen as an example here.
In my main ViewController.swift file, I have this to transfer the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//If identifier equals the hydrogen element go to the elements Swift file
if segue.identifier == "hydrogenSegue" {
let hydrogenAtomicNumberPassing = segue.destination as! hydrogenViewController
hydrogenAtomicNumberPassing.hydrogenAtomicNumberPassed = hydrogenAtomicNumber
let hydrogenAtomicMassPassing = segue.destination as! hydrogenViewController
hydrogenAtomicMassPassing.hydrogenAtomicMassPassed = hydrogenAtomicMass
}
}
In the hydrogenViewController.swift file I have this:
import UIKit
class hydrogenViewController: UIViewController {
var hydrogenAtomicNumberPassed: Int!
var hydrogenAtomicMassPassed: Float!
#IBOutlet weak var hydrogenInformationLabel: UILabel!
#IBOutlet weak var hydrogenAtomicNumberLabel: UILabel!
#IBOutlet weak var hydrogenAtomicMassLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting the background color
self.view.backgroundColor = UIColor.gray
//Converting hydrogen's atomic number from an Int to a String
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
hydrogenAtomicNumberLabel.text = "Atomic Number: \(hydrogenAtomicNumberString)"
//Converting hydrogen's atomic mass from a Float to a String
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
hydrogenAtomicMassLabel.text = "Atomic Mass: \(hydrogenAtomicMassString)"
}
}
I am getting the error at:
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
I'm assuming it would happen to this line also if I fix only that line:
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
I have this code in my "searchViewController" (the .swift file used for the table view):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected : \(indexPath.row)")
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",
bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as!
hydrogenViewController
self.navigationController?.pushViewController(hydrogenSearchSegue,
animated:true)
}
}
When I click on the "Hydrogen" cell in the table view it crashes to this error:
Hydrogen cell
The crash
When I click on the "H" button in this image it will take me to the hydrogen view controller:
Image of the Hydrogen Button in the simulator (Top Left)
Image of the Hydrogen View Controller
I want the hydrogen cell to segue over to the hydrogen view controller just like the button can.
When this same issue came up earlier I just had an issue with the name of the segue in the storyboard. However, because there is no visible segue from the table view, I don't know how to fix the issue.
I've tried this:
performSegue(withIdentifier: "hydrogenSegue", sender: nil)
I was thinking that I could just reuse the "hydrogenSegue" from the button to the view controller but I get a SIGABRT error. It just says that there is no segue with the name "hydrogenSegue." It would be best if I could just reuse that segue in a way because everything is already connected but I now found out that the "searchViewController" can't recognize the segue. Any help is appreciated and my main goal is to just get the cell that is clicked on to move over to the element's designated view. I tried to provide as much information as possible without making it to long and if there is any more information needed, I should be able to provide it.
well. first answer
in your hydrogenViewController try with this lines.
var hydrogenAtomicNumberPassed: Int?
var hydrogenAtomicMassPassed: Float?
override func viewDidLoad(){
super.viewDidLoad()
self.viewBackgroundColor = .gray
}
override func viewWillAppear(){
super.viewWillAppear()
if let number = hydrogenAtomicNumberPassed
{
hydrogenAtomicNumberLabel.text = "Atomic Number: \(number)"
}
if let mass = hydrogenAtomicMassPassed
{
hydrogenAtomicMassLabel.text = "Atomic Mass: \(mass)"
}
}
Now, the segues only "lives" between a couple viewControllers, if you have a third view controller, the last will not recognize him.
other thing, you are using segues and navigation controller, from my point of view, it's a bad idea mixed both, I mean, there are specific apps that can use both ways to present views, only is a advice.
if you want to pass data with pushviewcontroller only use this line
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as! hydrogenViewController
hydrogenSearchSegue.VAR_hydrogenViewController = YOURVAR_INYOURFILE
self.navigationController?.pushViewController(hydrogenSearchSegue, animated:true)
}
tell me if you have doubts, and I will try to help you.

how can I get tableview label value on button click in swift

I am new in swift and I want to get the value of label from tableview on button click
I am using code like this but it is getting crash
in cellforrowatindexpath
cell.btnsubmit.tag = indexPath.row
cell.btnsubmit.addTarget(self, action: #selector(buttonSelected), for: .touchUpInside)
#objc func buttonSelected(sender: UIButton){
print(sender.tag)
let cell = sender.superview?.superview as! PatientUpdateVCCell
surgery_date = cell.surgeryDateTextField.text!
discharge_date = cell.dischargeDateTextField.text!
follow_up_duration = cell.lblfolowup.text!
follow_up_date = cell.firstFollowUpTextField.text!
patient_status = cell.patientStatusTextView.text!
}
but it is getting crash. How can I achieve this
crash
Could not cast value of type 'UITableViewCellContentView' (0x11a794af0) to 'appname.PatientUpdateVCCell' (0x10ae74ae0).
According to your crash last superView is contentView then it's superView is the needed cell , so You need
let cell = sender.superview!.superview!.superview as! PatientUpdateVCCell
Target/action is pretty objective-c-ish. And view hierarchy math is pretty cumbersome.
A swiftier way is a callback closure which is called in the cell and passes the cell.
In the cell add a callback property and an IBAction. Connect the action to the button
var callback : ((UITableViewCell) -> Void)?
#IBAction func buttonSelected(_ sender: UIButton) {
callback?(self)
}
In cellForRow rather than the tag assign the closure
cell.callback = { currentCell in
self.surgery_date = currentCell.surgeryDateTextField.text!
self.discharge_date = currentCell.dischargeDateTextField.text!
self.follow_up_duration = currentCell.lblfolowup.text!
self.follow_up_date = currentCell.firstFollowUpTextField.text!
self.patient_status = currentCell.patientStatusTextView.text!
}
And delete the action method in the controller

How to Focus Accessibility On A Particular Segment in A UISegmentedControl

OK. This answer helps a lot. I can select an accessibility item when a screen is shown. I simply add
UIAccessibility.post(notification: .layoutChanged, argument: <a reference to the UI item to receive focus>)
to the end of my viewWillAppear() method, and the item receives focus.
However, in one of my screens, the item I want to receive focus is a UISegmentedControl, and, when focused, it always selects the first item, no matter which one is selected. Since I followed the excellent suggestion here, I have an accessibility label for each item in the control, and I'd like my focus to begin on whichever segment is selected.
Is there a way to do this? As a rule, I try to avoid "hacky" solutions (like the one I just referenced), but I'm willing to consider anything.
Thanks!
UPDATE: Just to add insult to injury, I am also having an issue with the item I want selected being selected, then a second later, the screen jumps the selection to the first item. That's probably a topic for a second question.
I created a blank project as follows to reproduce the problem:
The solution is taking the selectedIndex to display the selected segment and providing the appropriate segment object for the VoiceOver notification: easy, isn't it?
I naively thought that getting the subview in the segmented control subviews array with the selectedIndex would do the job but that's definitely not possible because the subviews can move inside this array as the following snapshot highlights (red framed first element for instance):
The only way to identify a unique segment is its frame, so I pick up the segmented control index and the frame of the selected segment to pass them to the previous view controller.
That will allow to display (index) and read out (frame that identifies the object for the notification) the appropriate selected segment when this screen will appear after the transition.
Hereafter the code snippets for the view controller that contains the 'Next Screen' button:
class SOFSegmentedControl: UIViewController, UpdateSegmentedIndexDelegate {
var segmentIndex = 0
var segmentFrame = CGRect.zero
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let segueName = segue.identifier {
if (segueName == "SegmentSegue") {
if let destVC = segue.destination as? SOFSegmentedControlBis {
destVC.delegate = self
destVC.segmentIndex = segmentIndex
destVC.segmentFrame = segmentFrame
}
}
}
}
#IBAction func buttonAction(_ sender: UIButton) { self.performSegue(withIdentifier: "SegmentSegue", sender: sender) }
func updateSegmentIndex(_ index: Int, withFrame frame: CGRect) {
segmentIndex = index
segmentFrame = frame
}
}
... and for the view controller that displays the segmented control:
protocol UpdateSegmentedIndexDelegate: class {
func updateSegmentIndex(_ index: Int, withFrame frame: CGRect)
}
class SOFSegmentedControlBis: UIViewController {
#IBOutlet weak var mySegmentedControl: UISegmentedControl!
var delegate: UpdateSegmentedIndexDelegate?
var segmentFrame = CGRect.zero
var segmentIndex = 0
var segmentFrames = [Int:CGRect]()
override func viewDidLoad() {
super.viewDidLoad()
mySegmentedControl.addTarget(self,
action: #selector(segmentedControlValueChanged(_:)),
for: .valueChanged)
mySegmentedControl.selectedSegmentIndex = segmentIndex
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(mySegmentedControl.subviews)
let sortedFrames = mySegmentedControl.subviews.sorted(by: { $0.frame.origin.x < $1.frame.origin.x})
for (index, segment) in sortedFrames.enumerated() { segmentFrames[index] = segment.frame }
if (self.segmentFrame == CGRect.zero) {
UIAccessibility.post(notification: .screenChanged,
argument: mySegmentedControl)
} else {
mySegmentedControl.subviews.forEach({
if ($0.frame == self.segmentFrame) {
UIAccessibility.post(notification: .screenChanged,
argument: $0)
}
})
}
}
#objc func segmentedControlValueChanged(_ notif: NSNotification) {
delegate?.updateSegmentIndex(mySegmentedControl.selectedSegmentIndex,
withFrame: segmentFrames[mySegmentedControl.selectedSegmentIndex]!) }
}
The final result is as follows:
Double tap to go to the next screen.
Select the next element to focus the second segment.
Double tap to select the focused element.
Get back to the previous screen thanks to the Z gesture natively known by iOS with the navigation controller. The delegate passes the index and the frame of the selected segment.
Double tap to go to the next screen.
The segment that was formerly selected is read out by VoiceOver and still selected.
You can now Focus Accessibility On A Particular Segment in A UISegmentedControl following this rationale.
I try to avoid "hacky" solutions (like the one I just referenced), but I'm willing to consider anything.
Unfortunately, this solution is a hacky one... sorry. However, it works and I couldn't find another one anywhere else: see it as a personal fix unless you get a cleaner one to share? ;o)
UPDATE... That's probably a topic for a second question.
I can't reproduce the behavior of your update: if you create a dedicated topic for this problem, please add the most detailed code and context so as to provide the most accurate solution.
i think this works~!
class VC {
let segment = UISegmentedControl()
func fucusSegment(index: Int) {
let item = segment.accessibilityElement(at: index )
UIAccessibility.post(notification: .layoutChanged, argument: item)
}
}

Many buttons to a single view controller

im developing an app that utilises many buttons( possibly 20 buttons) on one primary view controller that can are all able to activate a singular picker view within a pop up on a seperate view controller. i don’t think the answer is lots and lots segues. Is there a better approach I should be considering?
I’m thinking - some kind of multiuse segue that can be activated by any of the buttons, but nonidea how this is done.
Appreciate any advice
Mike
Set up all buttons to same action such as:
#IBAction func keyPressed(_ sender:UIButton){
// use button title string
self.keyString = sender.titleLabel?.text as! String
// or tag
self.keyTag= sender.tag?
self.performSegue(withIdentifier: "TheSegue", sender: self)
}
Then you would want to set up the View Controller that you are going to navigate to based on the state of the sender. So you would override the prepare:forSegue method as below.
override func prepare(for segue:UIStoryboardSegue, sender:Any?) {
let destController = segue.destination as! Dest_Controller_Class
// use tag or keyTitle to set controller attributes
// before view is shown
destController.keyTag = self.keyTag
destController.keyString = self.keyString
}
Now once you've navigated to the Dest_Controller_Class, you will have the properties of the button pressed locally in the view controller and could update the view as you see fit:
class Dest_Controller_Class: UIViewController {
var keyString: String?
var keyTag: Int?
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
if (keyString != nil) {
label.text = keyString;
// or likewise use tag
} else {
label.text = "keyString not set"
}
}
}

How to ignore the sender parameter UIButton

I was using a function as an #IBAction but now I want to use it as a normal function. But the problem is when I try to call the function it is asking me for the sender and is expecting a UIButton as a parameter.
How can I remove that sender so it doesn't affect my function?
Here is my function:
func addProductToCartButton(_ sender: UIButton) {
// Start animation region
let buttonPosition : CGPoint = sender.convert(sender.bounds.origin, to: self.productsTableView)
let indexPath = self.productsTableView.indexPathForRow(at: buttonPosition)!
let cell = productsTableView.cellForRow(at: indexPath) as! ProductTableViewCell
let imageViewPosition : CGPoint = cell.productImageView.convert(cell.productImageView.bounds.origin, to: self.view)
let imgViewTemp = UIImageView(frame: CGRect(x: imageViewPosition.x, y: imageViewPosition.y, width: cell.productImageView.frame.size.width, height: cell.productImageView.frame.size.height))
imgViewTemp.image = cell.productImageView.image
animationProduct(tempView: imgViewTemp)
// End animation region
}
Here is where I need to call the function:
func didTapAddToCart(_ cell: ProductTableViewCell) {
let indexPath = self.productsTableView.indexPath(for: cell)
addProductToCartButton( expecting UIBUTTON parameter)
}
I was trying to set the sender as nil but is not working. Do you have any idea?
Approach 1 (Recommended):
You can make that argument as optional:
#IBAction func addProductToCartButton(_ sender: UIButton?)
{
// Do your stuff here
}
Now you can call it like:
addProductToCartButton(nil)
Approach 2 (Not Recommended)
If you don't want to make the argument as optional, you can call it like:
addProductToCartButton(UIButton()) // It's not recommended
Approach 3 (Recommended)
Just write another utility function and add the code in it (Add the code written inside the IBAction to this function). Instead of calling IBAction from another function, call this utility function.
You need to refactor your code. The current implementation of addProductToCartButton uses the sender (the button) to determine an index path. And then the rest of the code is based on that index path.
You then have your didTapAddToCart method which attempts to call addProductToCartButton but you don't have the button at this point but it does have an index path.
I would create a new function that takes an index path as its parameter. Its implementation is most of the existing code in addProductToCartButton.
Here's the new function (which is mostly the original addProductToCartButton code):
func addProduct(at indexPath: IndexPath) {
let cell = productsTableView.cellForRow(at: indexPath) as! ProductTableViewCell
let imageViewPosition : CGPoint = cell.productImageView.convert(cell.productImageView.bounds.origin, to: self.view)
let imgViewTemp = UIImageView(frame: CGRect(x: imageViewPosition.x, y: imageViewPosition.y, width: cell.productImageView.frame.size.width, height: cell.productImageView.frame.size.height))
imgViewTemp.image = cell.productImageView.image
animationProduct(tempView: imgViewTemp)
// End animation region
}
Then redo addProductToCartButton as:
func addProductToCartButton(_ sender: UIButton) {
// Start animation region
let buttonPosition : CGPoint = sender.convert(sender.bounds.origin, to: self.productsTableView)
let indexPath = self.productsTableView.indexPathForRow(at: buttonPosition)!
addProduct(at: indexPath)
}
And finally, update didTapAddToCart:
func didTapAddToCart(_ cell: ProductTableViewCell) {
let indexPath = self.productsTableView.indexPath(for: cell)
addProduct(at: indexPath)
}
Extending Midhun MPs answer you can make your function call even simpler by providing a default value of nil:
#IBAction func addProductToCartButton(_ sender: UIButton? = nil) {
// Do your stuff here
}
Then you can call the function like this:
addProductToCartButton()
Well simplest approach for this type of logic is, pass nil value as parameter, it would be like this,
addProductToCartButton(nil)
Just make sure that you are not using any button property in your function. But if you are using button property then just simple add a check in your function like this,
func addProductToCartButton(_ sender: UIButton) {
if sender != nil {
//Do Something
}
}
Hope this will solve your issue, Thanks for reading this.

Resources