I've a TTTableViewController which follows TTTableViewController -> TTDataSource -> TTModel pattern. I've TTTableMoreButton and my list goes on to load more items when the user clicks on it.
How can I change the behaviour of this TTTableMoreButton? When the user came to the end of the list, I want it to behave as if it is clicked. In Facebook app, there is an implementation like this. I hope I could tell what I want.
Here is how to do it.
full disclosure: It is my code blog.
Here I've my own approach which i found out just before coneybeare's answer. I simply subclassed TTTableMoreButton and TTTableMoreButtonCell classes and in the "- (void)layoutSubviews" method, I detect that "Load More" button is appearing, and it should start loading more data if it is not already doing it.
I'm not sure which approach (coneybeaare's or mine) is the best and I'm looking forward for the comments about it.
AutoMoreTableItem.h
#interface AutoMoreTableItem : TTTableMoreButton {
}
#end
AutoMoreTableItem.m
#import "AutoMoreTableItem.h"
#implementation AutoMoreTableItem
#end
AutoMoreTableItemCell.h
#interface AutoMoreTableItemCell : TTTableMoreButtonCell {
}
#end
AutoMoreTableItemCell.m
#import "AutoMoreTableItemCell.h"
#import "AutoMoreTableItem.h"
#implementation AutoMoreTableItemCell
- (void)setObject:(id)object {
if (_item != object) {
[super setObject:object];
AutoMoreTableItem* item = object;
self.animating = item.isLoading;
self.textLabel.textColor = TTSTYLEVAR(moreLinkTextColor);
self.selectionStyle = TTSTYLEVAR(tableSelectionStyle);
self.accessoryType = UITableViewCellAccessoryNone;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
AutoMoreTableItem* moreLink = self.object;
if(moreLink.isLoading ==YES) {
return;
}
if (moreLink.model) {
moreLink.isLoading = YES;
self.animating = YES;
[moreLink.model load:TTURLRequestCachePolicyDefault more:YES];
}
}
#end
And of course, in the datasource implementation:
- (Class)tableView:(UITableView*)tableView cellClassForObject:(id) object {
if([object isKindOfClass:[AutoMoreTableItem class]]){
return [AutoMoreTableItemCell class];
} else {
return [super tableView:tableView cellClassForObject:object];
}
}
Related
When sharing an image from, eg Photos, I want my iOS app extension to appear in the list of apps available (next to Mail, Messages...) in order to receive the shared picture. Reading many sites reveals I have to subclass UIActivity and add it to an App Extension, what I did.
However I don't understand how I should instantiate my subclass, and it never gets called (No NSLog is displayed, item not in the sharing list).
How should I "instruct" iOS to use this subclass ?
myActivity.m:
#import "myActivity.h"
#implementation myActivity
- (NSString *)activityType {
// a unique identifier
return #"com.myapp.uniqueIdentifier";
}
- (NSString *)activityTitle {
// a title shown in the sharing menu
return #"Custom Activity";
}
- (UIImage *)activityImage {
// an image to go with our option
return [UIImage imageNamed:#"MyImage"];
}
+ (UIActivityCategory)activityCategory {
// which row our activity is shown in
// top row is sharing, bottom row is action
NSLog(#"UIActivityCategoryShare");
return UIActivityCategoryShare;
}
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems {
// return YES for anything that our activity can deal with
for (id item in activityItems) {
// we can deal with strings and images
if ([item isKindOfClass:[UIImage class]]) {
return YES;
}
}
// for everything else, return NO
return NO;
}
- (void)prepareWithActivityItems:(NSArray *)activityItems {
// anything we need to prepare, now's the chance
// custom UI, long running calculations, etc
// also: grab a reference to the objects our user wants to share/action
self.activityItems = activityItems;
}
- (UIViewController *)activityViewController {
// return a custom UI if we need it,
// or the standard activity view controller if we don't
return nil;
}
- (void)performActivity {
// the main thing our activity does
// act upon each item here
for (id item in self.activityItems) {
NSLog(#"YEY - someone wants to use our activity!");
NSLog(#"They used this object: %#", [item description]);
}
// notify iOS that we're done here
// return YES if we were successful, or NO if we were not
[self activityDidFinish:YES];
}
#end
And myActivity.h:
#import <UIKit/UIKit.h>
#interface myActivity : UIActivity
#property (nonatomic, strong) NSArray *activityItems;
#end
I have no idea how I should correctly name the title but I know exactly what my problem is (I will eventually edit the title later).
I am pretty new to Objective-C and I am still learning.
So, I have a class that contains a tableView (I will call it ClassA) and another with a normal UIView (ClassB). What I want to do, is to let a button appear when a row is selected.
I created in my ClassB.h file:
+(id)sharedInstance;
#property (retain, nonatomic) IBOutlet UIButton *btn;
-(void) showBtn :(BOOL) show;
And in my ClassB.m file:
#synthesize btn;
static ClassB *this = nil;
(+id) sharedInstance {
if(!this) {
#synchronized (self) {
this = [[ClassB alloc] init];
}
}
return this;
}
-(void)viewDidLoad {
[self showBtn:NO] //because I only want to let it appear when a row is selected.
[self.view addSubview:btn];
}
-(void) showBtn :(BOOL) show { // I called this method in classA.
if (show == NO) {
btn.hidden = YES;
} else {
btn.hidden = NO;
}
}
So when I launch my app, the button is hidden and stays hidden when I select a row. I debugged, and found that btn is nil when I called the method in ClassA. After some research, I found that the method is called for another instance, so here my question, what can I do, to get it called for the right instance?
EDIT
Here part of my ClassA.m
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row = [indexPath row];
[[ClassB sharedInstance] showBtn:YES];
}
Observation: The ClassB is a UIViewController which is wrong. UIViewControllers have viewDidLoad.
Implementation Suggestion:
The correct implementation for the requirement would be that you create a custom cell with a button. Hide the button in awakeFromNib method. in didSelectRowAtIndex set the cell.button.isHidden = YES.
This should alone take care of the requirement mentioned above.
I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html
I have a simple tvOS application starting with a UITabBarController and I wish the main view to have the focus when the app launches, not the tab bar.
I've tried playing with self.tabBarController.tabBar.userInteractionEnabled to remove temporarily the focus, but in vain. (Besides I do no like that kind of workaround)
Any clue?
Thanks in advance.
My original solution no longer works on tvOS 9.3, so I found a new one with subclassing UITabBarController:
#interface TVTabBarController : UITabBarController
#property (nonatomic) BOOL useDefaultFocusBehavior;
#end
#implementation TVTabBarController
- (UIView *)preferredFocusedView {
return self.useDefaultFocusBehavior ? [super preferredFocusedView] : self.selectedViewController.preferredFocusedView;
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
[super didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
self.useDefaultFocusBehavior = YES;
}
#end
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.tabBarController.tabBar.hidden = YES; // or do it in storyboard
}
If you use storyboard for initial UI setup, don't forget to set custom class TVTabBarController to your tab bar controller there.
Original solution:
Proposed approach with inheriting from UITabBarController didn't work for me because in fact -preferredFocusedView is called twice on startup, so I had to add a counter to return self.selectedViewController.preferredFocusedView for the first 2 calls. But it's a really hacky solution and there's no guarantee that it won't break in future.
So I found a much better solution: force focus update in appdelegate's -applicationDidBecomeActive: on the first call.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.tabBarController.tabBar.hidden = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
static BOOL forceFocusToFirstTab = YES;
if (forceFocusToFirstTab) {
forceFocusToFirstTab = NO;
[self.tabBarController.selectedViewController updateFocusIfNeeded];
}
}
The above approach mostly works but does not allow you to select a tab bar item with click as it returns the tabBar in that case when it should return the selectedItem. Here is an improved version which solves this by returning [super preferredViewController] instead of tabBar in the normal case. This version also hides the tab bar with alpha at launch so that it doesn't flicker in. There are probably more elegant ways to do the hiding.
#interface MWTabBarController ()
#property (nonatomic, assign) BOOL firstTime;
#end
#implementation MWTabBarController
- (void)viewDidLoad {
[super viewDidLoad];
self.firstTime = YES;
self.tabBar.alpha = 0;
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(unAlphaTabBar) userInfo:nil repeats:NO];
}
- (void) unAlphaTabBar
{
self.tabBar.alpha = 1;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (UIView *)preferredFocusedView {
if (self.firstTime) {
self.firstTime = NO;
return self.selectedViewController.preferredFocusedView;
}
else {
return [super preferredFocusedView];
}
}
I've found the solution, so if someone is interested, you just have to subclass UITabBarController and to override preferredFocusedView:
#interface ZTWTabBarController ()
#property (nonatomic, assign) BOOL firstTime;
#end
#implementation ZTWTabBarController
- (void)viewDidLoad {
[super viewDidLoad];
self.firstTime = YES;
}
- (UIView *)preferredFocusedView {
if (self.firstTime) {
self.firstTime = NO;
return self.selectedViewController.preferredFocusedView;
}
else {
return [super preferredFocusedView];
}
}
#end
I was able to achieve this effect very simply with the isHidden property of the UITabBar.
override func viewDidLoad() {
super.viewDidLoad()
self.tabBar.isHidden = true
}
When the user scrolls up to display the tab bar, the UITabBarController will unhide it automatically.
This is the easiest & cleanest solution in my opinion:
override var preferredFocusedView: UIView? {
if tabBar.hidden {
return selectedViewController?.preferredFocusedView
}
return super.preferredFocusedView
}
Since preferredFocusedView is deprecated in tvOS, you should override the preferredFocusEnvironments property instead
Swift 4.0
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if firsTime, let enviroments = selectedViewController?.preferredFocusEnvironments {
firsTime = false
return enviroments
}
return super.preferredFocusEnvironments
}
One of 35 header files in project (Handed over to me by some other developer; All of them contains same delegates declaration)
#interface ActivityDetailsCN : UIViewController <NSXMLParserDelegate,
AccountStatusDelegate, AccountTypeDelegate, DirectionDelegate, RecipientDelegate,
PriorityDelegate, DurationDelegate, CurrencyDelegate, OppTypeDelegate,
OppCategoryDelegate, DatePickerDelegate, SalutationDelegate, DepartmentDelegate,
LeadTypeDelegate, OwnershipDelegate, MailingDelegate, SourceDelegate,
StateDelegate, CommentsDelegate, CityDelegate, ZipCodeDelegate>
{
//Declaration of iVars goes here...
}
All the delegates declared here contains the same functions. Even their definitions, too.
Each of these delegates are declared before their respective ViewController header file Like this:
#protocol AccountStatusDelegate <NSObject>
- (void)cancelTapped;
- (void)doneTapped;
- (void)selectTapped:(NSString *)string;
#end
#interface AccountStatusVC : UIViewController <NSXMLParserDelegate> {
}
#property (unsafe_unretained) id <AccountStatusDelegate> delegate;
Implementation of cancelTapped:
- (void)cancelTapped {
[objPopOver dismissPopoverAnimated:YES];
}
Implementation of cancelTapped:
- (void)doneTapped {
[tblView reloadData];
[objPopOver dismissPopoverAnimated:YES];
}
Implementation of cancelTapped:
- (void)selectTapped:(NSString *)string
{
if ([string isEqualToString:#"US"])
isTextField = FALSE;
else if([string isEqualToString:#"Other"]) {
appDelegate.strCountry = #"";
isTextField = TRUE;
}
[tblView reloadData];
[objPopOver dismissPopoverAnimated:YES];
}
Now, Coming to the Question: I don't want to repeat it in each and every class (as it is now); I want to make it in cleaner way, Is there any possible solution available ?
Implement the delegate methods in a common superclass, and refactor all of the protocols to be one common TapCallbackDelegate protocol