Skip to content
Roland Moers edited this page Nov 14, 2018 · 14 revisions

This page will describe how to use RMActionController in your project.

Basic

RMActionController does not come with any content view (the one in the middle between all those buttons) for presentation. This means presenting an RMActionController only presents a set of buttons. For this task UIAlertController might be a better option.

The strength of RMActionController is that it can present any UIView as content view in an UIActionSheet/UIAlertController like fashion. To add a content view to RMActionController it is usually subclassed. This project contains two subclasses (RMCustomViewActionController and RMMapActionController) which give two examples for a subclass of RMActionController. In addition, you might want to take a look at the following projects for more examples on how to use RMActionController:

Subclassing

The remainder of this wiki page will explain how to create a subclass of RMActionController.

Creating your subclass

First create your own subclass of RMActionController. I assume you know how to do that. When creating your subclass you can specify a generic type. This type should be set to the class of the content view you are about to show. For example, if you are planning to present an MKMapView, the generic should be set to MKMapView. However, the generic type must be UIView or a subtype of UIView.

Objective-C

@interface RMMapActionController : RMActionController<MKMapView *>
@end

Swift

class MapActionController: RMActionController<MKMapView> {
}

Implementing your subclass

When subclassing RMActionController you only have to overwrite one method. This method is called actionControllerWithStyle:title:message:selectAction:andCancelAction:. For example, if you want to present an MKMapView your implementation may look like follows:

Objective-C

- (instancetype)initWithStyle:(RMActionControllerStyle)aStyle title:(NSString *)aTitle message:(NSString *)aMessage selectAction:(RMAction *)selectAction andCancelAction:(RMAction *)cancelAction {
    self = [super initWithStyle:aStyle title:aTitle message:aMessage selectAction:selectAction andCancelAction:cancelAction];
    if(self) {
        self.contentView = [[MKMapView alloc] initWithFrame:CGRectZero];
        self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
        
        NSDictionary *bindings = @{@"mapView": self.contentView};
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[mapView(>=300)]" options:0 metrics:nil views:bindings]];
        [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[mapView(200)]" options:0 metrics:nil views:bindings]];
    }
    return self;
}

Swift

required override init(style aStyle: RMActionControllerStyle, title aTitle: String?, message aMessage: String?, select selectAction: RMAction<MKMapView>?, andCancel cancelAction: RMAction<MKMapView>?) {
    super.init(style: aStyle, title: aTitle, message: aMessage, select: selectAction, andCancel: cancelAction);
    
    self.contentView = MKMapView(frame: .zero)
    self.contentView.translatesAutoresizingMaskIntoConstraints = false
    
    let bindings = ["contentView": self.contentView];
    self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "[contentView(>=300)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings))
    self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[contentView(200)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings))
}

Please make sure to set the contentView property of RMActionController to the content view you want to show. This property will be the only way for you to access the content view in any callbacks. Also, if the contentView is not set RMActionController will throw an exception when presenting.

If you are using Swift you also need to to overwrite the initializer for NSCoder.

Swift

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder);
}

Additional options

Sometimes you need to disable the blurred background for the content view you want to show (for example, this is the case for presenting an MKMapView). If you encounter such a case you need to overwrite the getter of the property disableBlurEffectsForContentView and always return true.

Objective-C

- (BOOL)disableBlurEffectsForContentView {
    return YES;
}

Swift

override var disableBlurEffectsForContentView:Bool {
    get {
        return true;
    }
    set {
    }
}

Presentation

Presenting your subclass of RMActionController works by using standard Apple API.

Objective-C

- (IBAction)openActionController:(id)sender {
    RMAction *selectAction = [RMAction<MKMapView *> actionWithTitle:@"Select" style:RMActionStyleDone andHandler:^(RMActionController<MKMapView *> *controller) {
        NSLog(@"Action controller selected location: %f, %f", controller.contentView.centerCoordinate.latitude, controller.contentView.centerCoordinate.longitude);
    }];
    
    RMAction *cancelAction = [RMAction<MKMapView *> actionWithTitle:@"Cancel" style:RMActionStyleCancel andHandler:^(RMActionController<MKMapView *> *controller) {
        NSLog(@"Action controller was canceled");
    }];
    
    RMMapActionController *actionController = [RMMapActionController actionControllerWithStyle:RMActionControllerStyleWhite title:@"Test" message:@"This is a map action controller.\nPlease select a location and tap 'Select' or 'Cancel'." selectAction:selectAction andCancelAction:cancelAction];
    
    //Now just present the action controller using the standard iOS presentation method
    [self presentViewController:actionController animated:YES completion:nil];
}

Swift

func openMapActionController() {
    let selectAction = RMAction<MKMapView>(title: "Select", style: RMActionStyle.done) { controller in
        print("Action controller selected location: ", controller.contentView.centerCoordinate.latitude, controller.contentView.centerCoordinate.longitude)
    }
    
    let cancelAction = RMAction<MKMapView>(title: "Cancel", style: RMActionStyle.cancel) { _ in
        print("Map action controller was canceled")
    }
    
    let actionController = MapActionController(style: .white, title: "Test", message: "This is a map action controller.\nPlease choose a date and press 'Select' or 'Cancel'.", select: selectAction, andCancel: cancelAction)
    
    //Now just present the action controller using the standard iOS presentation method
    present(actionController, animated: true, completion: nil)
}

Advanced Options

There are some options you might want to set before presenting your subclass of RMActionController:

  • disableBouncingEffects: Enables/Disables any bouncing effect when presenting your subclass of RMActionController.
  • disableMotionEffects: Enables/Disables any motion effect when tilting your device while your subclass of RMActionController is being presented.
  • disableBlurEffects: Enables/Disables any blur effect when presenting your subclass of RMActionController. The background of RMActionController will not be blurred when disabled.

In addition, RMActionController is a UIViewController. Therefore you can also set any property of UIViewController which might be necessary for presenting. For example, you might want to set the modal presentation style for adapting your subclass of RMActionController to the iPad. This might look as follows:

Objective-C

- (IBAction)openMapActionController:(id)sender {
    // Code as shown above
    ...

    //First we set the modal presentation style to the popover style
    actionController.modalPresentationStyle = UIModalPresentationPopover;
        
    //Then we tell the popover presentation controller, where the popover should appear
    actionController.popoverPresentationController.sourceView = self.tableView;
    actionController.popoverPresentationController.sourceRect = CGRectMake(...);
    
    //Now just present the action controller using the standard iOS presentation method
    [self presentViewController:actionController animated:YES completion:nil];
}

Swift

func openMapActionController() {
    // Code as shown above
    ...

    //First we set the modal presentation style to the popover style
    actionController.modalPresentationStyle = UIModalPresentationStyle.popover
    
    //Then we tell the popover presentation controller, where the popover should appear
    if let popoverPresentationController = actionController.popoverPresentationController {
        popoverPresentationController.sourceView = self.tableView
        popoverPresentationController.sourceRect = CGRect(...)
    }
    
    //Now just present the action controller using the standard iOS presentation method
    present(actionController, animated: true, completion: nil)
}

Final Note

You may use RMActionController in both your main application and your action extension showing an user interface. RMActionController only uses APIs that are safe to be used in extensions, too.