UIContextMenuConfiguration in objc - objective-c

I would like to implement the UIContextMenuConfiguration in objC
There are many examples in swift , but I have to close a matter in objc ...
So in swift I found examples like this
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu<UIAction>? in
let action = UIAction(__title: "Custom action", image: nil, options: []) { action in
// Put button handler here
}
return UIMenu<UIAction>.create(title: "Menu", children: [action])
}
return configuration
}
In objc I can not define the actions
I can only produce mistakes ...
this is my example code...
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
point:(CGPoint)point API_AVAILABLE(ios(13.0)){
BlogPost *blogPost = [self.blogPosts objectAtIndex:indexPath.row];
UIAction * lettura = [UIAction actionWithTitle:#"Leggi"
image:nil
identifier:nil
handler:^(UIAction *action){[self presentSF:indexPath];}
];
UIMenu * menu = [UIMenu menuWithTitle:#"" children:#[lettura]];
UIContextMenuConfiguration * config = [UIContextMenuConfiguration configurationWithIdentifier:nil
previewProvider:^ UIViewController* {
SFSafariViewController *previewSFController = [[SFSafariViewController alloc] initWithURL:blogPost.url entersReaderIfAvailable:NO];
previewSFController.preferredControlTintColor=[UIColor blackColor];
previewSFController.delegate = self;
return previewSFController;
}
actionProvider:nil];
return config;
}
Someone can help me
Thank you so much in advance for your help
Vanni

Sorry you people
I solved ...
I just had to pass an array...
actionProvider:^(NSArray* suggestedAction){return menu;}

- (UIContextMenuConfiguration*)tableView:(UITableView*)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath*)indexPath point:(CGPoint)point
{
UIContextMenuConfiguration* config = [UIContextMenuConfiguration configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu* _Nullable(NSArray<UIMenuElement*>* _Nonnull suggestedActions) {
NSMutableArray* actions = [[NSMutableArray alloc] init];
//Your Action
[actions addObject:[UIAction actionWithTitle:#"Favoritar!" image:[UIImage systemImageNamed:#"star"] identifier:nil handler:^(__kindof UIAction* _Nonnull action) {
[self updateFavoriteTournament:self.tournamentArray[indexPath.row][#"_id"]];
}]];
UIMenu* menu = [UIMenu menuWithTitle:#"" children:actions];
return menu;
}];
return config;
}

Related

Objective C - UICollectionViewListCell Add swipe to delete

Trying to implement "Swipe to Delete" API for UICollectionViewListCell.
I'm writing in Objective-C
the compiler is not auto-completing the code.
Any reasons? example code?
Swift example:
let listConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
listConfig.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in
guard let self = self else { return nil }
let action = UIContextualAction(style: .normal, title: "Done!", handler: actionHandler)
return UISwipeActionsConfiguration(actions: [action])
}
Any code example for Objective C?
trying to reach the following result:
UICollectionLayoutListConfiguration * listConfiguration = [[UICollectionLayoutListConfiguration alloc]initWithAppearance:UICollectionLayoutListAppearanceInsetGrouped];
[listConfiguration setTrailingSwipeActionsConfigurationProvider:^UISwipeActionsConfiguration* (NSIndexPath *indexPath) {
UIContextualAction *action = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:[NSLocalizedString(#"Delete", nil)capitalizedString] handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
}];
return [UISwipeActionsConfiguration configurationWithActions:#[action]];
}];

Objective C - Change System Font with Category

I want to change the default font for all UITextViews. It seems that the easiest way to do this is via custom category. I found this solution: Change the default systemFont used by controls and tried to implement it.
But my UITextViews are added programmatically so the awakeFromNib function is not called. I tried to move it to initWithFrame like this:
-(id)initWithFrame:(CGRect)frame
{
id result = [super initWithFrame:frame];
if (result) {
float size = [self.font pointSize];
NSString *stringfontstyle=self.font.fontName;
if([stringfontstyle rangeOfString:#"Bold"].location != NSNotFound) {
self.font = [UIFont fontWithName:#"Avenir-Black" size:size];
}
else if ([stringfontstyle rangeOfString:#"Italic"].location != NSNotFound) {
self.font = [UIFont fontWithName:#"Avenir-Oblique" size:size];
}
else if ([stringfontstyle rangeOfString:#"Medium"].location != NSNotFound) {
self.font = [UIFont fontWithName:#"Avenir-Medium" size:size];
}
else {
self.font = [UIFont fontWithName:#"Avenir-Roman" size:size];
}
}
return result;
}
Weird is that if my category contains initWithFrame function, the UITextView disappears. What is it that I'm missing?
Note: I'm using autoLayout so the initWithFrame is called with CGRectZero, but I suppose that isn't the problem.
EDIT:
The problem is that the font is null when the UITextView is initiated. So what method would be appropriate to place the code into?
when category contains a method, it overrides the class's method... and thats not good. subclassing would work.. method swizzling might be a way but...
why don't you just subclass UITextView - then you can keep your initWithFrame thingy or maybe override font
- (UIFont*)font {
if(!myFont) {
_myFont = xy;
}
id superFont = super.font;
if(![superFont.name isEqualTo:_myFont.name]) {
super.font = [myFont fontWithSize:superFont.pointSize];
}
return _myFont;
}
or setFont:
- (void)setFont:(UIFont*)newFont {
if(!myFont) {
_myFont = xy;
}
id thisFont = [_myFont fontWithSize:newFont.pointSize];
super.font = thisFont;

What have I got wrong with these code blocks?

I'm attempting to use Tony Million's Reachability within a new Swift based app. I have it implemented in another app I wrote in Obj C, but I'm having issues with getting the proper syntax in Swift. The code blocks are as follows:
override func viewDidLoad() {
super.viewDidLoad()
messageText.text = ""
var reach: Reachability = Reachability(hostName: "www.apple.com")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "reachabilityChanged", name: kReachabilityChangedNotification, object: nil)
reach.reachableBlock = Reachability()
{
dispatch_async(dispatch_get_main_queue(), {
self.messageText.text = "Enter search criteria...";
})
}
reach.unreachableBlock = Reachability()
{
dispatch_async(dispatch_get_main_queue(), {
self.messageText.text = "Attempting to contact network...";
})
}
reach.startNotifier()
}
AND
func reachabilityChanged(note: NSNotification)
{
var reach: Reachability = Reachability()
if(reach.isReachable())
{
messageText.text = "Enter search criteria...";
}
else
{
messageText.text = "Attempting to contact network...";
}
}
My issues are first, my "blocks" for "reachable" and "unreachable" are not the correct syntax and I'm at a loss for what is the proper syntax for these blocks. My second issue is with the "reachabilityChanged" function. I get an error stating "-[_TtC9icdDRPlus20SearchViewController reachabilityChanged]: unrecognized selector sent to instance" which I'm again at a loss. Condsider my Obj C code as follows:
-(void)viewDidLoad
{
[super viewDidLoad];
NSString *popUpShownOnce = #"YES";
NSInteger swipeCount = 0;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:popUpShownOnce forKey:#"popDisplayed"];
[defaults setInteger:swipeCount forKey:#"showswipearrows"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
[[UIApplication sharedApplication] setStatusBarHidden:NO];
Reachability * reach = [Reachability reachabilityWithHostname:#"somesite.com"];
reach.reachableBlock = ^(Reachability * reachability)
{
dispatch_async(dispatch_get_main_queue(), ^{
searchForText.placeholder = #"Enter search criteria...";
});
};
reach.unreachableBlock = ^(Reachability * reachability)
{
dispatch_async(dispatch_get_main_queue(), ^{
searchForText.placeholder = #"Attempting to contact network...";
});
};
[reach startNotifier];
}
-(void)reachabilityChanged:(NSNotification*)note
{
Reachability * reach = [note object];
if([reach isReachable])
{
searchForText.placeholder = #"Enter search criteria...";
}
else
{
searchForText.placeholder = #"Attempting to contact network...";
}
}
Assistance is greatly appreciated. The questions are as follows:
1. What is the proper translation for the reachable and unreachable blocks from Obj C to Swift?
2. If my addObserver call is correct, why might I get the unrecognized selector error? If not correct, what is the proper call?
Thanks in advance.
The correct syntax for closures is { (<params>) -> <return type> in <statements> }:
reach.reachableBlock = { (reachability) in
dispatch_async(dispatch_get_main_queue(), {
self.messageText.text = "Enter search criteria...";
})
}
With NSNotificationCenter, looks like you missed the colon in the selector argument:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "reachabilityChanged:", name: kReachabilityChangedNotification, object: nil)
Your error after making the changes that #Austin recommended is that self.reachabilityRef in SCNetworkReachabilitySetCallback is NULL.
try:
func reachabilityChanged(note: NSNotification!) {
var reach: Reachability! = Reachability(reachabilityRef: note.object as SCNetworkReachability)
if(reach.isReachable()) {
messageText.text = "Enter search criteria...";
} else {
messageText.text = "Attempting to contact network...";
}
}
UPDATE:
It would seem that SCNetworkReachability is not currently fully working with Swift: according to this: https://twitter.com/marksands/status/474717606004273152
I got it to work like this:
var reachability: Reachability?
override func viewDidLoad() {
super.viewDidLoad()
// Setup reachability
reachability = Reachability(hostName: "www.google.com")
reachability!.reachableBlock = { (reach) in
dispatch_async(dispatch_get_main_queue(), {
self.titleLabel.attributedText = Utilities.myAttributedText("Online", mySize: 18, myFont: "HelveticaNeue", myColor: UIColor.whiteColor())
self.titleLabel.sizeToFit()
})
}
reachability!.unreachableBlock = { (reach) in
dispatch_async(dispatch_get_main_queue(), {
self.titleLabel.attributedText = Utilities.myAttributedText("Offline", mySize: 18, myFont: "HelveticaNeue", myColor: UIColor.whiteColor())
self.titleLabel.sizeToFit()
})
}
reachability!.startNotifier()
}
this worked for me:
//Reachability
myReachabilityInstance = Reachability(hostName: "www.google.com")
myReachabilityInstance?.reachableOnWWAN = false
NSNotificationCenter.defaultCenter().addObserver(self, selector: "reachabilityDidChangeMethod", name: kReachabilityChangedNotification, object: nil)
myReachabilityInstance?.startNotifier()

UICollectionView Performing Updates using performBatchUpdates

I have a UICollectionView which I am trying to insert items into it dynamically/with animation. So I have some function that downloads images asynchronously and would like to insert the items in batches.
Once I have my data, I would like to do the following:
[self.collectionView performBatchUpdates:^{
for (UIImage *image in images) {
[self.collectionView insertItemsAtIndexPaths:****]
}
} completion:nil];
Now in place of the ***, I should be passing an array of NSIndexPaths, which should point to the location of the new items to be inserted. I am very confused since after providing the location, how do I provide the actual image that should be displayed at that position?
Thank you
UPDATE:
resultsSize contains the size of the data source array, self.results, before new data is added from the data at newImages.
[self.collectionView performBatchUpdates:^{
int resultsSize = [self.results count];
[self.results addObjectsFromArray:newImages];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
for (int i = resultsSize; i < resultsSize + newImages.count; i++)
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
[self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
} completion:nil];
See Inserting, Deleting, and Moving Sections and Items from the "Collection View Programming Guide for iOS":
To insert, delete, or move a single section or item, you must follow
these steps:
Update the data in your data source object.
Call the appropriate method of the collection view to insert or delete the section or item.
It is critical that you update your data source before notifying the
collection view of any changes. The collection view methods assume
that your data source contains the currently correct data. If it does
not, the collection view might receive the wrong set of items from
your data source or ask for items that are not there and crash your
app.
So in your case, you must add an image to the collection view data source first and then call insertItemsAtIndexPaths. The collection view will then ask the data source delegate function to provide the view for the inserted item.
I just implemented that with Swift. So I would like to share my implementation.
First initialise an array of NSBlockOperations:
var blockOperations: [NSBlockOperation] = []
In controller will change, re-init the array:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
blockOperations.removeAll(keepCapacity: false)
}
In the did change object method:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
if type == NSFetchedResultsChangeType.Insert {
println("Insert Object: \(newIndexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.insertItemsAtIndexPaths([newIndexPath!])
}
})
)
}
else if type == NSFetchedResultsChangeType.Update {
println("Update Object: \(indexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.reloadItemsAtIndexPaths([indexPath!])
}
})
)
}
else if type == NSFetchedResultsChangeType.Move {
println("Move Object: \(indexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.moveItemAtIndexPath(indexPath!, toIndexPath: newIndexPath!)
}
})
)
}
else if type == NSFetchedResultsChangeType.Delete {
println("Delete Object: \(indexPath)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.deleteItemsAtIndexPaths([indexPath!])
}
})
)
}
}
In the did change section method:
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
if type == NSFetchedResultsChangeType.Insert {
println("Insert Section: \(sectionIndex)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.insertSections(NSIndexSet(index: sectionIndex))
}
})
)
}
else if type == NSFetchedResultsChangeType.Update {
println("Update Section: \(sectionIndex)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.reloadSections(NSIndexSet(index: sectionIndex))
}
})
)
}
else if type == NSFetchedResultsChangeType.Delete {
println("Delete Section: \(sectionIndex)")
blockOperations.append(
NSBlockOperation(block: { [weak self] in
if let this = self {
this.collectionView!.deleteSections(NSIndexSet(index: sectionIndex))
}
})
)
}
}
And finally, in the did controller did change content method:
func controllerDidChangeContent(controller: NSFetchedResultsController) {
collectionView!.performBatchUpdates({ () -> Void in
for operation: NSBlockOperation in self.blockOperations {
operation.start()
}
}, completion: { (finished) -> Void in
self.blockOperations.removeAll(keepCapacity: false)
})
}
I personally added some code in the deinit method as well, in order to cancel the operations when the ViewController is about to get deallocated:
deinit {
// Cancel all block operations when VC deallocates
for operation: NSBlockOperation in blockOperations {
operation.cancel()
}
blockOperations.removeAll(keepCapacity: false)
}
I was facing the similar issue while deleting the item from index and this is what i think we need to do while using performBatchUpdates: method.
1# first call deleteItemAtIndexPath to delete the item from collection view.
2# Delete the element from array.
3# Update collection view by reloading data.
[self.collectionView performBatchUpdates:^{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:sender.tag inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
[self.addNewDocumentArray removeObjectAtIndex:sender.tag];
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
This help me to remove all the crash and assertion failures.

How to resume a CAAnimation after coming back from multitasking

in my app i have an array of CALayer that I have animated along a bezierPath. When I close and reopen the app my layers are not animating and not in the same position as before closing the app. I have implemented two methods, pauseLayer and resumeLayer that works when I trigger them with two buttons inside my app but they won't work after closing the app. The code is the following
- (void)pauseLayers{
for(int y=0; y<=end;y++)
{
CFTimeInterval pausedTime = [car[y] convertTime:CACurrentMediaTime() fromLayer:nil];
car[y].speed = 0.0;
car[y].timeOffset = pausedTime;
standardUserDefaults[y] = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults[y]) {
[standardUserDefaults[y] setDouble:pausedTime forKey:#"pausedTime"];
[standardUserDefaults[y] synchronize];
}
NSLog(#"saving positions");
}
}
-(void)resumeLayers
{
for(int y=0; y<=end;y++)
{
standardUserDefaults[y] = [NSUserDefaults standardUserDefaults];
car[y].timeOffset = [standardUserDefaults[y] doubleForKey:#"pausedTime"];
CFTimeInterval pausedTime = [car[y] timeOffset];
car[y].speed = 1.0;
car[y].timeOffset = 0.0;
car[y].beginTime = 0.0;
CFTimeInterval timeSincePause = [car[y] convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
car[y].beginTime = timeSincePause;
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
mosquitosViewController *mvc = [[mosquitosViewController alloc] init];
[mvc pauseLayers];
}
The problem with what you are trying to do above is that you are creating a completely new instance of your view controller, which is not the one that was showing onscreen. That's why nothing happens when you send the pauseLayers message.
What you should do is register to receive notifications for when your app goes to and comes from the background and call the appropriate methods (pauseLayers and resumeLayers) when that notification arrives.
You should add the following code somewhere in your mosquitosViewController implementation (I usually do so in viewDidLoad):
// Register for notification that app did enter background
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(pauseLayers)
name:UIApplicationDidEnterBackgroundNotification
object:[UIApplication sharedApplication]];
// Register for notification that app did enter foreground
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(resumeLayers)
name:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication]];
I write a Swift 4 version extension based on #cclogg and #Matej Bukovinski answers from this thread. All you need is to call layer.makeAnimationsPersistent()
Full Gist here: CALayer+AnimationPlayback.swift, CALayer+PersistentAnimations.swift
Core part:
public extension CALayer {
static private var persistentHelperKey = "CALayer.LayerPersistentHelper"
public func makeAnimationsPersistent() {
var object = objc_getAssociatedObject(self, &CALayer.persistentHelperKey)
if object == nil {
object = LayerPersistentHelper(with: self)
let nonatomic = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
objc_setAssociatedObject(self, &CALayer.persistentHelperKey, object, nonatomic)
}
}
}
public class LayerPersistentHelper {
private var persistentAnimations: [String: CAAnimation] = [:]
private var persistentSpeed: Float = 0.0
private weak var layer: CALayer?
public init(with layer: CALayer) {
self.layer = layer
addNotificationObservers()
}
deinit {
removeNotificationObservers()
}
}
private extension LayerPersistentHelper {
func addNotificationObservers() {
let center = NotificationCenter.default
let enterForeground = NSNotification.Name.UIApplicationWillEnterForeground
let enterBackground = NSNotification.Name.UIApplicationDidEnterBackground
center.addObserver(self, selector: #selector(didBecomeActive), name: enterForeground, object: nil)
center.addObserver(self, selector: #selector(willResignActive), name: enterBackground, object: nil)
}
func removeNotificationObservers() {
NotificationCenter.default.removeObserver(self)
}
func persistAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = layer.animation(forKey: key) {
persistentAnimations[key] = animation
}
}
}
func restoreAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = persistentAnimations[key] {
layer.add(animation, forKey: key)
}
}
}
}
#objc extension LayerPersistentHelper {
func didBecomeActive() {
guard let layer = self.layer else { return }
restoreAnimations(with: Array(persistentAnimations.keys))
persistentAnimations.removeAll()
if persistentSpeed == 1.0 { // if layer was playing before background, resume it
layer.resumeAnimations()
}
}
func willResignActive() {
guard let layer = self.layer else { return }
persistentSpeed = layer.speed
layer.speed = 1.0 // in case layer was paused from outside, set speed to 1.0 to get all animations
persistAnimations(with: layer.animationKeys())
layer.speed = persistentSpeed // restore original speed
layer.pauseAnimations()
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"1");
mosquitosViewController *mvc = [[mosquitosViewController alloc] init];
[mvc pauseLayers];
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
*/
}
See my answer to this post for details on how to restart an animation after multitasking:
Restoring animation where it left off when app resumes from background