From Swift to Objective C - updateCallDurationForVisibleCells - objective-c

I'm trying to covert Speakrbox code in Objective-C.
I have already converted most of the code but I have a little problem with this one:
private func updateCallDurationForVisibleCells() {
/*
Modify all the visible cells directly, since -[UITableView reloadData] resets a lot
of things on the table view like selection & editing states
*/
let visibleCells = tableView.visibleCells as! [CallSummaryTableViewCell]
guard let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows else { return }
for index in 0..<visibleCells.count {
let cell = visibleCells[index]
let indexPath = indexPathsForVisibleRows[index]
guard let call = call(at: indexPath) else { return }
cell.durationLabel?.text = durationLabelText(forCall: call)
}
}
I tried to convert it, and here is what I have:
-(void) updateCallDurationForVisibleCells :(id)sender {
/*
Modify all the visible cells directly, since -[UITableView reloadData] resets a lot
of things on the table view like selection & editing states
*/
_visibleCells = _tableview.visibleCells;
if(_indexPathsForVisibleRows == _tableview.indexPathsForVisibleRows) { return ; }
int index;
for (index=0; index<_visibleCells.count; index ++)
{
UICollectionViewCell *cell = _visibleCells[index];
NSIndexPath *indexPath = _indexPathsForVisibleRows[index];
UITableViewCell* call;
if((call = [self.tableView cellForRowAtIndexPath:indexPath]))
{ return ; }
}
}
Could any one please help me to convert this Swift code into Objective-C? The code doesn't compile for the reason that I didn't know how to convert this line of code:
cell.durationLabel?.text = durationLabelText(forCall: call)
Also, I don't know if I did it in the right way, especially the conversion of guard let.
you will find here Call and durationLabelText functions that I have used in pdateCallDurationForVisibleCells function:
private func call(at indexPath: IndexPath) -> SpeakerboxCall? {
return callManager?.calls[indexPath.row]
}//swift
private func durationLabelText(forCall call: SpeakerboxCall) -> String? {
return call.hasConnected ? callDurationFormatter.format(timeInterval: call.duration) : nil
}//Swift

Here's a fixed up version of your Objective-C code. Please note that when you see var or let in Swift, you are creating a local variable. Do not create a bunch a needless instance variables or properties in the Objective-C code when you should just use local variables.
- (void)updateCallDurationForVisibleCells {
/*
Modify all the visible cells directly, since -[UITableView reloadData] resets a lot
of things on the table view like selection & editing states
*/
NSArray *indexPathsForVisibleRows = self.tableView.indexPathsForVisibleRows;
if(!indexPathsForVisibleRows) { return ; }
NSArray *visibleCells = self.tableview.visibleCells;
for (NSInteger index = 0; index < _visibleCells.count; index++) {
CallSummaryTableViewCell *cell = visibleCells[index];
NSIndexPath *indexPath = indexPathsForVisibleRows[index];
SomeDataType *call = [self call:indexPath];
if (!call) { return; }
cell.durationLabel.text = [self durationLabelText:call];
}
}
There is some information missing which means this conversion may not be 100% correct.
You haven't shown the Objective-C signature of the call: method so I can't be sure how to call it properly and I don't know what data type to use for the call variable. Replace SomeDataType with the proper type.
You haven't shown the Objective-C signature of the durationLabelText: method so I can't be 100% sure about the proper way to call it.
If you post those details in your question I can be sure this answer is updated properly.

Edit: Thanks to users like #rmaddy, newbies like me get to learn how to write answers.Adding the final full code part.
Skip to the final part of the answer to get the working code.
In case you want to know where you went wrong, read the full answer.
There are multiple things that are possibly not right here, let me address what I know:
The guard statement is used for graceful exit. I'm no Java expert, but consider this to be like Exceptions in JAVA. The Obj-C code for your first guard would be:
if (nil == _tableview.indexPathsForVisibleRows){
return
}
This also means that there's no necessity for the _indexPathsForVisibleRows variable.
I'd recommend this article to understand guard better:
https://ericcerney.com/swift-guard-statement/
With respect to your second guard, please pay attention to detail.
call(at: indexPath) is not the same as cellForRowAtIndexPath.
call(at: indexPath) is a class function that returns a datatype, say CallType. cellForRowAtIndexPath is a UITableView function, that returns a cell.
The cell in the below line is more likely to be of type CallSummaryTableViewCell
UICollectionViewCell *cell = _visibleCells[index];
Regarding durationLabelText(), it seems to be a class function in the swift file whose declaration would be something like:
//Assume the type of "call" is CallType
private func durationLabelText(forCall call : CallType) -> String{
//your code here
}
The Objective-C equivalent would be:
-(NSString*) durationLabelTextForCall: (id<CallType>) call{
//your code here
}
The Objective-C call would be:
cell.durationLabel.text = [self durationLabelTextForCall: call]
So, the final code would be like this:
-(NSString*) durationLabelTextForCall: (id<CallType>) call{
//your code here
}
-(id<CallType>) callAtIndexPath:(NSIndexPath) indexPath{
//your code here
}
- (void)updateCallDurationForVisibleCells {
/*
Modify all the visible cells directly, since -[UITableView reloadData] resets a lot
of things on the table view like selection & editing states
*/
if (nil == _tableview.indexPathsForVisibleRows){
return
}
NSArray *visibleCells = self.tableview.visibleCells;
for (index=0; index<visibleCells.count; index ++)
{
CallSummaryTableViewCell *cell = visibleCells[index];
NSIndexPath *indexPath = _tableview.indexPathsForVisibleRows[index];
id<CallType> call = [self callAtIndexPath:indexPath];
if (nil == call){
return
}
cell.durationLabel.text = [self durationLabelTextForCall: call]
}
}

Related

How to transfer Object c to Swift aout collectionVIewLayout

I have took below website as reference, and tried to convert this to Swift code. Refer to “collectioViewLayout” part, I have difficulties to make things in order.
Appreciated that.UICollectionVIew circlesLayout
The original code is as below:-
- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
if ([[self.collectionView.delegate class] conformsToProtocol:#protocol(UICollectionViewDelegateFlowLayout)]) {
return [(id)self.collectionView.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
}
return CGSizeMake(0, 0);}
My revised swift code as follows:-
func sizeForItem(at indexPath: IndexPath?) -> CGSize {
if type(of: collectionView.delegate) is UICollectionViewDelegateFlowLayout {
if let indexPath = indexPath {
return collectionView!.delegate?.collectionView!(collectionView!, layout: self, sizeForItemAt: indexPath) ?? CGSize.zero
}
return CGSize.zero
}
return CGSize(width: 0, height: 0)
}
However, some errors appeared. (see below)
Part 1) Cast from 'UICollectionViewDelegate?.Type' to unrelated type 'UICollectionViewDelegateFlowLayout' always fails
Part 2) Incorrect argument labels in call (have ':layout:sizeForItemAt:', expected':targetIndexPathForMoveFromItemAt:toProposedIndexPath:')
Is there anyone who can guide me how to fix those problem into correct swift code please?
The Objective-C code checks the type of collectionView.delegate, but in Swift you have to do a cast to tell the compiler that you are sure that it is indeed of type UICollectionViewDelegateFlowLayout{
func sizeForItem(at indexPath: IndexPath?) -> CGSize {
// "as?" does the cast, evaluating the expression to nil if it fails.
if let delegate = collectionView!.delegate as? UICollectionViewDelegateFlowLayout,
// you can combine these two checks into one if statement. Just separate them with ","
let indexPath = indexPath {
return delegate.collectionView?(collectionView, layout: self, sizeForItemAt: indexPath) ?? .zero
}
return .zero
}

converting Obj-c to swift, long press gesture

i can't seem to get this to work, i'm trying to implant a long press gesture on uicollectionview
found this code here in SO, now i just need to convert it to swift, so i can use it probely
obj-c
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateEnded) {
return;
}
CGPoint p = [gestureRecognizer locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:p];
if (indexPath == nil){
NSLog(#"couldn't find index path");
} else {
// get the cell at indexPath (the one you long pressed)
UICollectionViewCell* cell =
[self.collectionView cellForItemAtIndexPath:indexPath];
// do stuff with the cell
}
}
my approach in swift
func handleLongPress(gestureRecognizer: UILongPressGestureRecognizer){
if (gestureRecognizer.state != UIGestureRecognizerState.Ended){
return
}
let p: CGPoint = gestureRecognizer.locationInView(self.collectionView)
let indexPath: NSIndexPath = self.collectionView.indexPathForItemAtPoint(p)!
if (indexPath == nil) { // Error here 'NSIndexPath' is not convertible to 'UIGestureRecognizerState'
println("couldn't find index path")
} else {
let cell: UICollectionViewCell = self.collectionView.cellForItemAtIndexPath(indexPath)
}
}
appreciate any help, it fails at if (indexPath == nil) with the error 'NSIndexPath' is not convertible to 'UIGestureRecognizerState'
Try to use optional binding to find out whether an optional contains a value. indexPathForItemAtPoint returns an optional value.
if let indexPath = self.collectionView.indexPathForItemAtPoint(p)
{
let cell: UICollectionViewCell = self.collectionView.cellForItemAtIndexPath(indexPath)
}
else
{
println("couldn't find index path")
}
Read about optionals in Apple's swift book.
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID309
You can also use optional chaining and map over the optional indexpath.
let cell = collectionView.indexPathForItemAtPoint(p).map {
self.collectionView.cellForItemAtIndexPath($0)
}
if let cell = cell {
// Do something with non-optional cell
} else {
println("No cell for point")
}
The cell variable will be a UICollectionViewCell? (optional) but that is the return type of the cellForItemAtIndexPath call anyway.
If you can continue with or return the optional cell you can skip the whole if let section especially if you don't need to print the error (or do if cell == nil { println("No cell for point") }
See this blogpost for some more ways to handle optionals: How I Handle Optionals In Swift

Check if mapView already contains an annotation

I have a method of adding secondary nearby annotations (ann2) when I tap on another annotation (ann1). But when I deselect and re-select the exact same annotation (ann1) the ann2 re-creates it self and is getting added again. Is there a way to check if the annotation already exists on the map and if yes then do nothing otherwise add the new annotation. I have already checked this: Restrict Duplicate Annotation on MapView but it did not help me.. Any advice is appreciated. This is what I have so far:
fixedLocationsPin *pin = [[fixedLocationsPin alloc] init];
pin.title = [NSString stringWithFormat:#"%#",nearestPlace];
pin.subtitle = pinSubtitle;
pin.coordinate = CLLocationCoordinate2DMake(newObject.lat, newObject.lon);
for (fixedLocationsPin *pins in mapView.annotations) {
if (MKMapRectContainsPoint(mapView.visibleMapRect, MKMapPointForCoordinate (pins.coordinate))) {
NSLog(#"already in map");
}else{
[mapView addAnnotation:pin];
}
In this case I get the log already on map but I also get the drop animation of the annotation adding to the map. Any ideas?
Thank you in advance..
Your for loop isn't checking if the annotation is on the screen, it is checking if the coordinates of the pin are currently within the visible area. Even if it was checking if the pin object was already in the mapView.annotations it would never be true, because you've only just created pin a few lines earlier, it can't possibly be the same object as on in the mapView.annotations. It might though have the same coordinates and title, and that's what you need to check:
bool found = false;
for (fixedLocationsPin *existingPin in mapView.annotations)
{
if (([existingPin.title isEqualToString:pin.title] &&
(existingPin.coordinate.latitude == pin.coordinate.latitude)
(existingPin.coordinate.longitude == pin.coordinate.longitude))
{
NSLog(#"already in map");
found = true;
break;
}
}
if (!found)
{
[mapView addAnnotation:pin];
}
Annotations array exist in map object so you just have to check
if ( yourmap.annotations.count==0)
{
NSLog(#"no annotations");
}
NSNumber *latCord = [row valueForKey:#"latitude"];
NSNumber *longCord = [row valueForKey:#"longitude"];
NSString *title = [row valueForKey:#"name"];
CLLocationCoordinate2D coord;
coord.latitude = latCord.doubleValue;
coord.longitude = longCord.doubleValue;
MapAnnotation *annotation = [[MapAnnotation alloc]initWithCoordinate:coord withTitle:title];
if([mkMapView.annotations containsObject:annotation]==YES){
//add codes here if the map contains the annotation.
}else {
//add codes here if the annotation does not exist in the map.
}
if (sampleMapView.annotations.count > 0) {
sampleMapView.removeAnnotation(detailMapView.annotations.last!)
}
Following my comment on Craig's answer, I think the solution could look like something like this :
import MapKit
extension MKMapView {
func containsAnnotation(annotation: MKAnnotation) -> Bool {
if let existingAnnotations = self.annotations as? [MKAnnotation] {
for existingAnnotation in existingAnnotations {
if existingAnnotation.title == annotation.title
&& existingAnnotation.coordinate.latitude == annotation.coordinate.latitude
&& existingAnnotation.coordinate.longitude == annotation.coordinate.longitude {
return true
}
}
}
return false
}
}
This code allows you to check if a mapView contains a given annotation. Use this in a "for" loop on all your annotations:
for annotation in annotations {
if mapView.containsAnnotation(annotation) {
// do nothing
} else {
mapView.addAnnotation(annotation)
}
PS: this works well if you need to add new annotations to a mapView. But if you need also to remove entries, you may have to do the opposite: check that each existing annotation exists in the new array of annotations ; if not, remove it.
Or you could remove everything and add everything again (but then you will have the change animated ...)

How to programmatically open an NSComboBox's list?

I've been around this for a while.. I thought this should be an easy task, but it isn't =D
What I am trying to do, is to display the combobox's list when the user clicks the combobox but not specifically in the button.
Any Idea?
Thanks in advance!
This answer fits the title of the question, but not question itself. Omer wanted to touch a text field and have the box popup.
This solution shows the popup when the user enters text.
I found this answer on cocoabuilder from Jens Alfke. I reposted his code here. Thanks Jens.
original cocoabuilder post: (http://www.cocoabuilder.com/archive/cocoa)
#interface NSComboBox (MYExpansionAPI)
#property (getter=isExpanded) BOOL expanded;
#end
#implementation NSComboBox (MYExpansionAPI)
- (BOOL) isExpanded
{
id ax = NSAccessibilityUnignoredDescendant(self);
return [[ax accessibilityAttributeValue:
NSAccessibilityExpandedAttribute] boolValue];
}
- (void) setExpanded: (BOOL)expanded
{
id ax = NSAccessibilityUnignoredDescendant(self);
[ax accessibilitySetValue: [NSNumber numberWithBool: expanded]
forAttribute: NSAccessibilityExpandedAttribute];
}
I used this code in my controlTextDidChange: method.
- (void) controlTextDidChange:(NSNotification *) aNotification {
NSTextField *textField = [aNotification object];
NSString *value = [textField stringValue];
NSComboBox *box = [self comboBox];
if (value == nil || [value length] == 0) {
if ([box isExpanded]) { [box setExpanded:NO]; }
} else {
if (![box isExpanded]) { [box setExpanded:YES]; }
}
}
Returns true if the NSComboBox's list is expanded
comboBox.cell?.isAccessibilityExpanded() ?? false
Open the NSComboBox's list
comboBox.cell?.setAccessibilityExpanded(true)
Close the NSComboBox's list
comboBox.cell?.setAccessibilityExpanded(false)
Ref. jmoody’s answer.
You can use the following code line:
[(NSComboBoxCell*)self.acomboBox.cell performSelector:#selector(popUp:)];
Put
comboBoxCell.performSelector(Selector("popUp:"))
Into
override func controlTextDidChange(obj: NSNotification) {}
is what I ended up with. Thanks #Ahmed Lotfy
Here's the full code, it works for me on OSX 10.11
override func controlTextDidChange(obj: NSNotification) {
if let comboBoxCell = self.comboBox.cell as? NSComboBoxCell {
comboBoxCell.performSelector(Selector("popUp:"))
}
}
Thanks to jmoody and Jens Alfke mentioned above. Here is a SWIFT translation of the above solution.
import Cocoa
class CComboBoxEx: NSComboBox {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
}
func isExpanded() -> Bool{
if let ax:AnyObject? = NSAccessibilityUnignoredDescendant(self) {
if ax!.accessibilityAttributeValue(NSAccessibilityExpandedAttribute) != nil {
return true
}
}
return false
}
func setExpanded (bExpanded:Bool) {
if let ax:AnyObject? = NSAccessibilityUnignoredDescendant(self) {
ax!.accessibilitySetValue(NSNumber(bool: bExpanded), forAttribute: NSAccessibilityExpandedAttribute)
}
}
}
NSComboBox was not designed to work this way. Because the user may want to edit the text in the control, they'll need to be able to click it without unexpectedly popping up the choices.
You would need to subclass NSComboBoxCell and change this behavior ... but then you'd have a standard-looking control that does not behave in a standard way. If you're determined to do this, take a look at the open source version of NSComboBoxCell. The interesting methods appear to be -popUpForComboBoxCell: and friends.
Based on the other answers I wrote this solution (tested with Xcode 10.2.1, Swift 5). It uses the same ideas but it's a little shorter.
// Put this extension for NSComboBox somewhere in your project
import Cocoa
public extension NSComboBox {
var isExpanded: Bool{
set {
cell?.setAccessibilityExpanded(newValue)
}
get {
return cell?.isAccessibilityExpanded() ?? false
}
}
}
// Set your corresponding NSViewController as NSComboBoxDelegate
// in the storyboard and add this piece of code
// to expand the combobox when the user types
class MyViewController: NSViewController, NSComboBoxDelegate {
func controlTextDidChange(_ notification: Notification) {
guard let comboBox = notification.object as? NSComboBox else { return }
if comboBox.isExpanded == false {
comboBox.isExpanded = true
}
}
}

"EXC_BAD_ACCESS" error when cell begins scrolling back in view?

I've got some (I think) pretty basic code for creating cell content from a data source, and everything works fine when the display loads. However, when I start scrolling around to view other text (up or down) the code fails with 'GDB: Program received signal: "EXEC_BAD_ACCESS"'. Here's the code that fills out the display for the various sections; each section has similar code:
id cell = (UITableViewCell *)[tableView
dequeueReusableCellWithIdentifier:CellIdentifier
];
titledCell = [[[TitledCell alloc]
initWithFrame:CGRectZero
reuseIdentifier:CellIdentifier
] autorelease
];
switch (tableSection) {
case TABLE_SECTION_1:
if (cell == nil) {
dataKey = #"a key from data source";
dataFromSource = [viewData objectForKey:dataKey];
titledCell.title.text = dataKey;
titledCell.contents.text = dataFromSource;
cell = titledCell;
break;
}
case TABLE_SECTION_2:
...
}
return cell;
As I was following the code, I noticed that the code skips the cell creation when scrolling the cell back into view, because cell != nil. If it skips, that means that cell contains the same contents as the first time it was created, right? Why is giving me trouble?
I usually get EXEC_BAD_ACCESS when I forget to retain something. If you've got an autorelased object, it may work the first time through but not work the second time.
Run the program with debugging and use Xcode to figure out what line it's crashing on. That will be more helpful than anything else.
I think the EXEC_BAD_ACCESS might caused by the:
titledCell.title.text = dataKey;
titledCell might get dealloced and when accessing the property there'll be a EXEC_BAD_ACCESS exception.
You can turn on the NSZombieEnabled env virable in: Group & Files -> Extutables -> Your App -> Get Info -> Arguments
Can't be 100% sure with the code sample you've given but a good guess would be the break statement is within the if block. So it should look like:
switch( tableSection ) {
case TABLE_SECTION_1:
if( cell == nil ) {
dataKey = #"a key from data source";
dataFromSource = [ viewData objectForKey:dataKey ];
titledCell.title.text = dataKey;
titledCell.contents.text = dataFromSource;
cell = titledCell;
}
break;
case TABLE_SECTION_2:
...
}
Unfortunately, changes have made this question academic, so I can't really tell if any of the answers given are correct. The current code looks a little closer to this:
id cell = (UITableViewCell *)[tableView
dequeueReusableCellWithIdentifier:CellIdentifier
];
if (cell == nil) {
titledCell = [[[TitledCell alloc]
initWithFrame:CGRectZero
reuseIdentifier:CellIdentifier
] autorelease
];
switch (tableSection) {
case TABLE_SECTION_1:
dataKey = #"a key from data source";
dataFromSource = [viewData objectForKey:dataKey];
titledCell.title.text = dataKey;
titledCell.contents.text = dataFromSource;
cell = titledCell;
break;
case TABLE_SECTION_2:
...
}
return cell;
...so that most of the code is now in the "if (cell == nil)" block (which makes more sense), and it works fine. I wish I understood what was wrong, but thanks for your Answers anyway!