TableView Add Buttons

Hi,

What is the best way of adding buttons to a TTMSFMXNativeUITableViewItem programatically and have an onclick event that knows about the item that the button was in?

Regards,

Ken

I'm not sure what you mean with adding a button, do you mean you want to access the AccessoryView and add a native UIButton? The OnClick event could be bundled for every button which has a unique Tag property?


Kind Regards, 
Pieter

I have created the following component for use with the table view. It is creating a UIButton fine and when clicked does delete the item but after deleting a few items it crashes with no error when clicked.

Am I doing something wrong:

unit KenFMXTableView;

interface

uses
  System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Graphics,
  FMX.TMSNativeUIBaseControl, FMX.TMSNativeUITableView, System.TypInfo
  {$IFDEF IOS}
  ,iOSApi.UIKit, iOSApi.Foundation, Macapi.ObjectiveC, iOSapi.CocoaTypes, iOSapi.CoreGraphics, MacApi.ObjcRuntime
  {$ENDIF}
    ,FMX.TMSNativeUICore;
type

  {$IFDEF IOS}
  IBtnDelegate=interface(NSObject)
    procedure BtnPressed; cdecl;
  end;

  TBtnDelegate=class(TOCLocal)
  private
    TVItem:TTMSFMXNativeUITableViewItem;
  public
   constructor Create;
   function  GetObjectiveCClass:PTypeInfo; override;
   procedure BtnPressed; cdecl;
  end;
{$ENDIF}

  TKenFMXTableViewItem=class(TTMSFMXNativeUITableViewItem)
  private
    FTitle:String;
    FDescription:String;
    {$IFDEF IOS}
      BtnDelegate:TBtnDelegate;
    {$ENDIF}
  published
    property Title:String read FTitle write FTitle;
    property Description:String read FDescription write FDescription;
  end;

  TKenFMXTableViewItems=class(TTMSFMXNativeUITableViewItems)
  public
    function CreateItemClass:TCollectionItemClass; override;
  end;

  TKenFMXTableViewSection=class(TTMSFMXNativeUITableViewSection)
  public
    function CreateItems:TTMSFMXNativeUITableViewItems; override;
  end;

  TKenFMXTableViewSections=class(TTMSFMXNativeUITableViewSections)
  public
    function CreateItemClass:TCollectionItemClass; override;
  end;

  [ComponentPlatformsAttribute(pidiOSSimulator or pidiOSDevice)]
  TKenFMXTableView=class(TTMSFMXNativeUITableView)
  private
  protected
    { Protected declarations }
  public
    BitmapToUse:TBitmap;
    constructor Create(AOwner:TComponent); override;
    function CreateSections:TTMSFMXNativeUITableViewSections; override;
    function GetItemHeight(ASection,ARow:Integer):Single; override;
    function GetHeightForHeaderInSection(ASection: Integer): Single; override;
    function GetItemStyle(ASection,ARow:Integer):TTMSFMXNativeUITableViewItemStyle; override;
    {$IFDEF IOS}
    procedure DoItemCreateCell(Sender:TObject;var ACell:UITableViewCell;AItemStyle:TTMSFMXNativeUITableViewItemStyle;ASection,ARow:Integer); override;
    procedure DoItemCustomizeCell(Sender:TObject;ACell:UITableViewCell;AItemStyle:TTMSFMXNativeUITableViewItemStyle;ASection,ARow: Integer); override;
    {$ENDIF}
  published
    { Published declarations }
  end;

procedure Register;

implementation

uses
  FMX.Dialogs;

{$IFDEF IOS}
constructor TBtnDelegate.Create;
begin
  inherited Create;
end;

function TBtnDelegate.GetObjectiveCClass:PTypeInfo;
begin
  Result:=TypeInfo(IBtnDelegate);
end;

procedure TBtnDelegate.BtnPressed;
var
  S,I:Integer;
  TV:TKenFMXTableView;
begin
  if Assigned(TVItem) then
  begin
    S:=TVItem.Section.Index;
    I:=TVItem.Index;
    TV:=TVItem.OwnerTableView as TKenFMXTableView;
    TV.BeginRefreshing;
    TV.Sections[S].Items.DisposeOf;
    TV.EndRefreshing;
    TVItem:=Nil;
  end;
end;
{$ENDIF}

function TKenFMXTableViewItems.CreateItemClass:TCollectionItemClass;
begin
  Result:=TKenFMXTableViewItem;
end;

function TKenFMXTableViewSection.CreateItems:TTMSFMXNativeUITableViewItems;
begin
  Result:=TKenFMXTableViewItems.Create(OwnerTableView,Self);
end;

function TKenFMXTableViewSections.CreateItemClass: TCollectionItemClass;
begin
  Result:=TKenFMXTableViewSection;
end;

constructor TKenFMXTableView.Create(AOwner:TComponent);
begin
  inherited;
  if (csDesigning in ComponentState) and not
    ((csReading in Owner.ComponentState) or (csLoading in Owner.ComponentState)) then
  begin
    Options.Header:='Ken''s View';
    Options.ToolBar:=False;
  end;
end;

function TKenFMXTableView.CreateSections:TTMSFMXNativeUITableViewSections;
begin
  Result:=TKenFMXTableViewSections.Create(Self);
end;

{$IFDEF IOS}
procedure TKenFMXTableView.DoItemCreateCell(Sender:TObject;var ACell:UITableViewCell;AItemStyle:TTMSFMXNativeUITableViewItemStyle;
  ASection,ARow:Integer);
var
  Title:UILabel;
  Description:UILabel;
  R:NSRect;
  It:TTMSFMXNativeUITableViewItem;
  KenIt:TKenFMXTableViewItem;
  Btn:UIButton;
begin
  R.Origin.X:=5;
  R.Origin.Y:=5;
  R.Size.Width:=32;
  R.Size.Height:=32;
  Btn:=TUIButton.Wrap(TUIButton.Wrap(TUIButton.OCClass.Alloc).InitWithFrame(R));
  It:=GetItem(ASection,ARow);
  if Assigned(It) and (It is TKenFMXTableViewItem) then
  begin
    KenIt:=It as TKenFMXTableViewItem;
    KenIt.BtnDelegate:=TBtnDelegate.Create;
    Btn.addTarget(KenIt.BtnDelegate.GetObjectID,       // target
      sel_getUid('BtnPressed'),                        // action
      UIControlEventTouchDown);                        // event
  end;
  ACell.ContentView.AddSubview(Btn);
  R.Origin.X:=5;
  R.Origin.Y:=5;
  R.Size.Width:=ACell.Frame.Size.Width-100;
  R.Size.Height:=25;
  Title:=TUILabel.Wrap(TUILabel.Wrap(TUILabel.OCClass.Alloc).InitWithFrame(R));
  Title.SetFont(TUIFont.Wrap(TUIFont.OCClass.BoldSystemFontOfSize(16)));
  Title.SetHighlightedTextColor(TUIColor.Wrap(TUIColor.OCClass.BlackColor));
  Title.SetTextColor(TUIColor.Wrap(TUIColor.OCClass.WhiteColor));
  ACell.ContentView.AddSubview(Title);
  R.Origin.X:=5;
  R.Origin.Y:=Title.Frame.Origin.Y+Title.Frame.Size.Height;
  R.Size.Width:=ACell.Frame.Size.Width-100;
  Description:=TUILabel.Wrap(TUILabel.Wrap(TUILabel.OCClass.Alloc).InitWithFrame(R));
  Description.SetHighlightedTextColor(TUIColor.Wrap(TUIColor.OCClass.BlackColor));
  Description.SetTextColor(TUIColor.Wrap(TUIColor.OCClass.WhiteColor));
  ACell.ContentView.AddSubview(Description);
end;

procedure TKenFMXTableView.DoItemCustomizeCell(Sender:TObject;ACell: UITableViewCell;
  AItemStyle:TTMSFMXNativeUITableViewItemStyle;ASection,ARow:Integer);
var
  Title:UILabel;
  Description:UILabel;
  It:TTMSFMXNativeUITableViewItem;
  KenIt:TKenFMXTableViewItem;
  Str:NSString;
  R:NSRect;
  Img:UIImage;
  Btn:UIButton;
begin
  Btn:=TUIButton.Wrap(ACell.ContentView.Subviews.ObjectAtIndex(0));
  Title:=TUILabel.Wrap(ACell.ContentView.Subviews.ObjectAtIndex(1));
  Description:=TUILabel.Wrap(ACell.ContentView.Subviews.ObjectAtIndex(2));
  It:=GetItem(ASection,ARow);
  if Assigned(It) and (It is TKenFMXTableViewItem) then
  begin
    KenIt:=It as TKenFMXTableViewItem;
    if Assigned(BitmapToUse) then
    begin
      if Assigned(KenIT.BtnDelegate) then
        KenIT.BtnDelegate.TVItem:=It;
      Img:=ImageFromBitmap(BitmapToUse,-1);
      Btn.setImage(Img,0);
      ACell.SetImage(Nil);
//      ACell.SetImage(Img);
    end else
      ACell.SetImage(Nil);
    if Assigned(Title) then
    begin
      Title.SetText(NSSTREx(KenIt.Title));
      R:=Title.Frame;
      if Assigned(BitmapToUse) then
        R.Origin.X:=5+32+5{+Acell.Image.Size.Width}
      else
        R.Origin.X:=5;
      Title.SetFrame(R);
    end;
    if Assigned(Description) then
    begin
      Description.SetText(NSSTREx(KenIt.Description));
      R:=Description.Frame;
      if Assigned(Acell.Image) then
        R.Origin.X:=15+Acell.Image.Size.Width
      else
        R.Origin.X:=5;
      Description.SetFrame(R);
    end;
  end;
end;
{$ENDIF}

function TKenFMXTableView.GetItemHeight(ASection,ARow:Integer):Single;
begin
  Result:=Options.RowHeight;
end;

function TKenFMXTableView.GetHeightForHeaderInSection(ASection: Integer): Single;
begin
  Result:=Options.SectionHeaderHeight;
end;

function TKenFMXTableView.GetItemStyle(ASection,ARow:Integer):TTMSFMXNativeUITableViewItemStyle;
begin
  Result:=isTableViewCellStyleCustom;
end;

procedure Register;
begin
  RegisterComponents('Ken', [TKenFMXTableView]);
end;

end.

The issue you are having is a pointer issue. The item is removed, along with the button which still holds a reference to the item. The DoItemCreateCell is only called when necessary, if the cell is being created. When scrolling, removing cells, the DoItemCustomizeCell is called to apply new properties.

I would assume you will need to update the button event handler, and reference to the new item as well.

If you are testing in the simulator, I would suggest to test on a real device because it behaves differently and might give you more clues on what is going on.

Kind Regards, 
Pieter