Swift iOS: Drag & Drop between two Containers fails - ios

I'm entering the world of Swift, stepping forward slowly...
What I'm trying to have is an UIViewController with two Container Views, each of them having its own UIViewController with an UITableView, where it is possible to drag a table entry from the right container's table, and drop it on the left container's table. The parent UIViewController shall care about everything around that drag'n'drop operation.
I therefore added a Long Press Gesture Recognizer to the UIViewController and wired it accordingly:
#IBAction func longPress(sender: UILongPressGestureRecognizer) {
switch(sender.state) {
case UIGestureRecognizerState.Began: startDragDrop(sender)
case UIGestureRecognizerState.Ended: endDragDrop(sender)
default: return
}
}
where
func startDragDrop(sender: UILongPressGestureRecognizer) {
var startPoint: CGPoint = sender.locationInView(self.structureElementContainer!.tableView)
var indexPath = self.structureElementContainer.tableView.indexPathForRowAtPoint(startPoint)!
println("startPoint: \(startPoint.x) \(startPoint.y)")
println("\(indexPath.section) \(indexPath.row)")
}
When long-clicking on a table entry, there is a crash in the "var startPoint" line:
2015-03-20 15:45:59.822 xxx.20[10274:18100886] -[UIView tableView]: unrecognized selector sent to instance 0x7a14fa90
2015-03-20 15:45:59.828 xxx.20[10274:18100886] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView tableView]: unrecognized selector sent to instance 0x7a14fa90'
What's going wrong here?
Thanks a lot for your support
[Added 23-mar-2015]
I tried now to follow Mundi's hint, but without success up to now.
This is the view controller fragment of the parent, where I added outlets for the two containers:
class xxxDetails: UIViewController {
#IBOutlet weak var viewTitle: UILabel!
#IBOutlet weak var aaaElementsContainer: UIView!
#IBOutlet weak var bbbStructureContainer: UIView!
#IBOutlet var gestureRecognizer: UILongPressGestureRecognizer!
var structureElementsController: StructureElementsController!
override func viewDidLoad() {
super.viewDidLoad()
}
The containers embed a UITableViewController each:
class aaaElementsController: UITableViewController {...}
class bbbElementsController: UITableViewController {...}
I tried to create an Outlet for those controllers in the parent UIViewController, without success.
I tried to create a property in the UITableViewController which refers to itself, to be able to access it from the parent, without success.
It seems that I'm possibly following a wrong approach. So the question is now for me: When embedding two child UITableViewControllers via ContainerViews in a parent UIView, how do I access those children correctly, so that I'm able to have gesture recognition in the parent which is able to follow a drag&drop from the one child to the other?

Your self.structureElementContainer does not have a tableView property. Presumably the controller of that view has one. You have to refactor to have the main view keep a reference to the controllers, not the views.

Well, Mundi's response did not solve my problem directly, but it put me on the track of a sequence of information that led to the solution... ;-)
I was pretty surprised that things may be so simple. I was stuck on the fact that I did not get a valid handle to the subview controllers. However, to get these handles, you just have to care about the embed segue during viewDidLoad:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "aaaElementsSegue" {
aaaElementsController = segue.destinationViewController as AaaElementsController
}
}
After that, the aaaElementsController is valid and allows access to the table view in the child view.
And, as this is true for the second child view, I'm now able to handle the drag&drop between those two child views.

Related

How to get context of what Object corresponds to a certain UISegmentedControl

Below you can find an image of a UITableViewCell subclass that I've created. Each cell corresponds to a game that is played between two contestants, and has a winner, defined by the selected control (that in the editor says "first" and "second"). If this winner value is changed, I want to update that game's information and store it in Core Data. The problem is that when I create an IBAction connection to my storyboard, and it gets triggered when the winner is changed, it has no context or knowledge of what game it belongs to, and therefore I can't figure out which game to update the winner of.
How do I get context of the game that was acted on in this IBAction winnerChanged() method? Could I somehow use the superView or superClass of the UISegmentedControl to get more information about the UITableViewCell as a whole?
#IBAction func winnerChanged(_ sender: UISegmentedControl) {
os_log("Saving games because winner was changed", type: .debug)
// need to know what game to change the winner of here
self.allGames.forEach({ updateOrCreateGame(game: $0) })
}
Here's my code for my UITableViewCell subclass:
class ConferenceResultsTableViewCell: UITableViewCell {
#IBOutlet weak var gameWinnerControl: UISegmentedControl!
#IBOutlet weak var confidenceTextField: UITextField!
#IBOutlet weak var confidenceAverageAllUsersLabel: UILabel!
}
Typically you create such #IBActions right inside the cell. From your question, I assume that the method #IBAction func winnerChanged(_ sender: UISegmentedControl) defined somewhere in your controller.
It would be much easier and also resolve your question if you create a dedicated class for your cell with an implementation of that method.
This will allow you to hook your cell with a concrete CoreData's object (through identifier in an example below). All you need to do is to call setup method somewhere from cellForRow of your controller/data source.
final class MatchCell: UITableViewCell {
#IBAction func winnerChanged(_ sender: UISegmentedControl) {
...
}
func setup(with match: Match) {
self.matchId = mathc.id
...
}
}
if you still need that updateOrCreateGame method inside your controller you can pass a callback onUpdateRequested: (Match) -> Void in setup method and call that callback from winnerChanged method.

Should we make tapGesture component an IBAction or IBOutlet to capture the tap event?

When I want a tap response on my main View in my ViewController
A. I could create an IBOutlet as below
class ViewController: UIViewController {
#IBOutlet var tapGesture: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
tapGesture.addTarget(self, action: #selector(tapped))
}
#objc private func tapped(_: UITapGestureRecognizer) {
print("Log is here")
}
}
Or
B. I could an IBAction on the TapGesture such as below
class ViewController: UIViewController {
#IBAction func tapGestureAction(_ sender: Any) {
print("Log is here")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Is there a preferred approach of one above the other? If not, which situation should we use A approach, and which we should use B approach?
Option B, i.e. just having the #IBAction outlet would be preferred when you already created your UITapGestureRecognizer in the storyboard, as this encapsulates as much logic as possible in the storyboard, reducing the overhead of reading unnecessary code and potential regressions if/when the code is refactored (but the storyboard remains unchanged).
You can still mark the #IBAction private (as it's effectively the same as using an #objc attribute). Also, if you need to access the gesture recognizer itself, you can have a regular #IBOutlet with a didSet to modify it, or change sender: Any to sender: UITapGestureRecognizer to access it in the action.
It is an interesting question, from my perspective this depends on how much from your application is in the storyboard or you want it explicitly written in the code.
My recommendation will be if you are doing something small and it should be done fast to use your storyboard. But if you have a big project with a big team then it will be better to have it in the code.
The other thing that can be a key factor for these approaches will be who is the owner of the reference and do you want to have some interactions of the gesture. For example, I have a gesture that should be enabled in specific cases and for others, it should be disabled. For this, you need to have a reference in the code.
What I'm trying to explain is that you should think for criteria like how and when you can use this gesture. And based on this to decide if you need less code or reference to the gesture or whatever you need

Passing data between views in ONE ViewController in Swift

All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.

Add a xib multiple times to one UIView

Maybe it is the wrong idea completely to use the same xib multiple times in one viewcontroller, but it looked in some way better than creating an x amount of views with the same labels and the same buttons..
And of course some fun with xibs:)
To give you a quick impression of what I achieved so far:
In the class belonging to the xib, I created a delegate so I could catch a button press in my main view controller.
import UIKit
protocol TimerViewDelegate : class {
func timerButtonTapped(buttonState : NSInteger)
}
class TimerView: UIView {
var delegate : TimerViewDelegate?
var currentButtonState = 0
#IBOutlet var view: UIView!
#IBOutlet var timerLabel: UILabel!
#IBOutlet var button: UIButton!
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("TimerView", owner: self, options: nil)
self.addSubview(self.view)
}
#IBAction func buttonTapped(sender:UIButton) {
if currentButtonState == 0{
currentButtonState = 1
} else{
currentButtonState = 0
}
self.delegate?.timerButtonTapped(currentButtonState)
}
}
I know it is not super fancy stuff, but at the moment I'm only evaluating if its any use to do this at all.
In my main view controller I registered outlets for the xibs in a way like:
#IBOutlet weak var timerView1 : TimerView!
#IBOutlet weak var timerView2 : TimerView!
#IBOutlet ...
And in viewDidLoad():
self.timerView1.delegate = self
self.timerView2.delegate = self
self...
Later I can catch the button presses in the following method:
func timerButtonTapped(buttonState: NSInteger) {
println("button press from nib, with state \(buttonState)")
}
From here it does make a difference if I press the button from the top xib or another one, since they keep track of their own buttonstate.
But how can I distinguish the different xibs from each other like this?
I can give the xibs themselves a tag, but I don't know if that has any use. Also talking to their labels from my main view, will have a simular problem..
Even if this is a completely wrong approach of using xibs, I'm still interested how to solve this.
Thank you for your time!
You pretty much have your solution already, you just need to improve your protocol method specification, basically by adding the TimerView that is passing on the button press.
(compare to a delegate protocol like UITableViewDelegate, where the table view always passes itself...)
So, something like:
protocol TimerViewDelegate : class {
func timerButtonTapped(sender: TimerView, buttonState : NSInteger)
}
and then the delegate can find out which TimerView is associated and do something with it.
Incidentally, it's likely best to store the TimerView instances in an array, sorted in some way, so that you can easily access the details.
You can use the tag property safely. Apple documentation says:
An integer that you can use to identify view objects in your application.

Global Variables vs Direct Conection to pass data between view controllers ios

I don't think the title uses the right terminology so I'll try to clarify now.
I have two view controllers which i want to pass data between. view 1 has a tableView and the 2nd view has a MKMapView. Now in the corresponding controllers I want when you click on a cell in view 1 it sends you to the place on the map which the cell indicates eg. a New York cell would send to a map of new York. So I tried in the didSelectRowAtIndexPath that I create an instance of the second controller which would transfer the data to it. But when I did that it would always return a nil value in the second view controller.
So instead I created a 3rd swift file which has some global variables and when I transfer the data via them it works perfectly. Why is this so?
Cheers.
EDIT: Before the I went via a third file
Code for the ViewController 1
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell = array[indexPath.row]
var viewController2 = ViewController2()
viewController2.textLabel1 = selectedCell.name
coord.textLabel2 = selectedCell.coordinate
}
Code for ViewController 2
#IBOutlet weak var textLabel1: UILabel!
#IBOutlet weak var textLabel2: UILabel!
Code for View Controller 2 going via 3rd file
#IBOutlet weak var textLabel1: UILabel!
#IBOutlet weak var textLabel2: UILabel!
override func viewDidLoad() {
textLabel1.text = globalVar1
textLabel2.text = globalVar2
}
Code from the 3rd File
var globalVar1: String!
var globalVar2: String!
So from the comments below I take it that in the first way the textLabels hadn't been initialised yet, so the values I assigned to them where turned into nil values. Is this correct? If so how would you do the first way correctly
If I had to guess, and I would because I cannot comment for more info yet(i get in trouble ).
It's because you are trying to assign it to an outlet.
The outlet has not been set yet which means when it is set (I think around ViewDidLoad)
the outlet will be set to nil.
The properties should however be retained if the object hasn't gone out of the heap that is.
PSedu code:
first view :
Your table view
Second View:
your map view
How to work
In you second view:
Add a NSMutableArray *valueArraytoGet in .h file,set its property
#synchronize it in .m file
Now in your first view at didSelectRowAtIndex method
create object of Second View Controller
and assign data as
SecondViewController *object=......
object.valueArraytoGet=[assign ur value array here]....
Hope it will help

Resources