I have simple subclass of UIViewController (code below).
If I attach inputAccessoryView, my viewcontroller is never deallocated. If I do not set inputAccessoryView in viewDidLoad, dealloc is called as expected.
Am I missing something?
#interface IMTestViewController : UIViewController
#property (nonatomic, strong) UIView *messageInputView;
#property(nonatomic, readwrite, strong) UIView *inputAccessoryView;
#end
#implementation IMTestViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.inputAccessoryView = self.messageInputView;
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (UIView *)messageInputView
{
if (_messageInputView == nil)
{
_messageInputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 45)];
_messageInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
}
return _messageInputView;
}
#end
I have ran out of ideas.
Thank you.
Unfortunately for me #rdelmar's answer didn't work. After some time spent trying to solve it I found this article: http://derpturkey.com/uitextfield-docked-like-ios-messenger/
My goal is to have the input accessory view visible even if the keyboard is not, exactly like in all IM apps. I previously subclassed my UIViewController custom class to allow it to become first responder and returned my custom subview as inputAccessoryView. This was preventing the view controller from being dealloced. Now I subclass the controller's view to achieve the same thing as recommended in the link above, everything seems to work fine.
EDIT: after some more testing I can confirm the custom UIView is dealloced just fine.
EDIT 2: only downside is that you can't make the keyboard appear in viewWillAppear, the inputAccessoryView is not already added to the view hierarchy and can't become first responder.
This question is rather old, but I came across it in 2019 when trying to use an inputAccessoryView in iOS 12.
The deallocation problem still exists today and the first solution proposed in the article mentioned in dvkch's answer does not work either. The second solution in the article (involving animations) is just too much work and does not work well when the user dismisses the keyboard interactively via a UIScrollView with scrollView.keyboardDismissMode = .interactive.
The best approach I could come up with is just setting the first responder UITextField or UITextView inputAccessoryView to nil on viewDidDisappear. That gets rid of the memory leak entirely and does not seem to have any side-effects or downsides.
So here's a full Swift 4.2 example:
class MyViewController: UIViewController {
/// You could also add your text field or text view programmatically,
/// but let's say it's coming from a .xib for now...
#IBOutlet private weak var myTextField: UITextField!
/// This is required for the inputAccessoryView to work.
override internal var canBecomeFirstResponder: Bool {
return true
}
/// Here's a nice empty red view that will be used as an
/// input accessory.
private lazy var accessoryView: UIView = {
let accessoryView = UIView()
accessoryView.backgroundColor = UIColor.red
accessoryView.frame.size = CGSize(
width: view.frame.size.width,
height: 45
)
return accessoryView
} ()
override var inputAccessoryView: UIView? {
return accessoryView
}
/// This is required to avoid leaking the `inputAccessoryView`
/// when the keyboard is open and the `UIViewController`
/// is deallocated.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
myTextField.inputAccessoryView = nil
}
}
Related
I want to make a taller navigation bar and I am trying to do this using a subclass of UINavigationBar.
Here is my subclass of UINavigationBar:
import UIKit
class TallerNaviBar: UINavigationBar {
override func sizeThatFits(size: CGSize) -> CGSize {
var newSize:CGSize = CGSizeMake(size.width, 87)
return newSize
}
}
And in my ViewController which is embedded in a navigation controller, I write the following code in the viewDidLoad() function
self.navigationController!.setValue(TallerNaviBar(frame: CGRectMake(0, 0, self.view.frame.size.width, 87)), forKeyPath: "navigationBar")
There is no error reported, but I get nothing when I run the code. A completely blank view. .
Any suggestion? Or some other way of changing the height? Thanks.
update for iOS 11 and beyond
apple doesn't want you messing with the navigation bar height, so don't touch it
see here: https://openradar.appspot.com/32912789
and here: https://forums.developer.apple.com/thread/88202#274620
class TallerNaviBar: UINavigationBar {
override func sizeThatFits(size: CGSize) -> CGSize {
var newSize:CGSize = CGSizeMake(self.superview!.frame.size.width, 87)
return newSize
}
}
I don't do swift, but the answer is easy, here's ObjC
- (CGSize)sizeThatFits:(CGSize)size {
return CGSizeMake([self superview].frame.size.width, 40);
}
Here's my interpretation in Swift:
import UIKit
class TallerNaviBar: UINavigationBar {
override func sizeThatFits(size: CGSize) -> CGSize {
var newSize:CGSize = CGSizeMake(superview.width, 87)
return newSize
}
}
The problem you will have isn't with this method, this is the easy part, the problem is forcing the navigation controller to always use this navigation bar
I subclass and resize everything in IOS, including navigation controllers and tabbarcontrollers, in order to enforce that all navigation controllers use this navigation bar, you must subclass a navigationController and only use this navigationcontroller throughout your app, here's how the subclass works, this is Obj C, so you'll have to translate it:
#interface NSHNavigationController () <UINavigationBarDelegate, UINavigationControllerDelegate>
{
}
#implementation NSHNavigationController
#pragma mark Initialization
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self NSHSetupNavigationController];
}
return self;
}
- (instancetype)initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass
{
self = [super initWithNavigationBarClass:navigationBarClass toolbarClass:toolbarClass];
if (self) {
[self NSHSetupNavigationController];
}
return self;
}
- (id)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super initWithRootViewController:rootViewController];
if (self) {
[self NSHSetupNavigationController];
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self NSHSetupNavigationController];
}
return self;
}
- (void)dealloc
{
[self setInternalDelegate:nil];
[self setExternalDelegate:nil];
}
#pragma mark Setup
- (void)NSHSetupNavigationController
{
[self setValue:[[NSHNavigationBar alloc]init] forKeyPath:#"navigationBar"];
}
this is the line that will do it for you:
[self setValue:[[NSHNavigationBar alloc]init] forKeyPath:#"navigationBar"];
Oh yeah, and make sure you are subclassing the nav bar, you said you were, but here's how you do it, it's simple:
#import "NSHNavigationBar.h"
#implementation NSHNavigationBar
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
NSDictionary *attributes = #{NSForegroundColorAttributeName:[UIColor whiteColor],
NSFontAttributeName:fontMagicForRegularNSHFont};
[self setTitleTextAttributes:attributes];
[self setTranslucent:true];
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
- (CGSize)sizeThatFits:(CGSize)size {
return CGSizeMake([self superview].frame.size.width, heightResizer(40));
}
- (void)layoutSubviews {
[super layoutSubviews];
}
So, in summary, subclass both the UINavigationBar and the UINavigationController and you are set, this will allow you to manipulate the navigation bar whenever you'd like, you can also type cast your view controller's navigation bars, this is a little nuts and will confuse a lot of people, but here it goes:
-(CustomNavigationBar *)navBar {
return (id)[self.navigationController navigationBar];
}
put the stuff above in your view controller and then you call it like this:
[[self navBar] setBackGroundColor:[UIColor blueColor]];
This will successfully typecast your navigationcontroller to be your custom navigation bar, if you want to change the height of the nav bar from here, then you can do something like this:
Here is a simple Swift 3 solution based on minimal subclassing. First subclass UINavigationBar.:
class CustomNavigationBar: UINavigationBar {
override func sizeThatFits(_ size: CGSize) -> CGSize {
let newSize :CGSize = CGSize(width: self.frame.size.width, height: 54)
return newSize
}
}
Create the navigation controller in code and use the initializer that takes your custom navigation bar class.
let nav = UINavigationController(navigationBarClass: CustomNavigationBar.self,
toolbarClass: nil)
All existing behavior for UINavigationBar is preserved and your custom height is adopted.
The Problem
I am trying to use UISearchController to search for a destination on a map view. I want the UISearchBar to appear in the navigation bar, but I can't seem to make it do so without it showing a cancel button to the right of it:
This Cancel button has disappeared at times, whilst I'm playing around, but I can't get it to not appear now I have got the search table showing how I want it to:
I'm sure there must be something small I'm doing ever so slightly wrong, but I can't work out what it is...
My Code
self.resultsViewController = [UITableViewController new];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:self.resultsViewController];
self.searchController.searchResultsUpdater = self;
self.searchController.hidesNavigationBarDuringPresentation = false;
self.searchController.delegate = self;
self.searchBar = self.searchController.searchBar;
self.searchBar.placeholder = self.stage.title;
self.searchBar.searchBarStyle = UISearchBarStyleMinimal;
self.definesPresentationContext = true;
self.navigationItem.titleView = self.searchBar;
self.resultsTableView = self.resultsViewController.tableView;
self.resultsTableView.dataSource = self;
self.resultsTableView.delegate = self;
There is a way easier way...
For iOS 8, and UISearchController, use this delegate method from UISearchControllerDelegate:
func didPresentSearchController(searchController: UISearchController) {
searchController.searchBar.showsCancelButton = false
}
Don't forget to set yourself as the delegate: searchController.delegate = self
Updated in light of comments
UISearchBar has a property (see the Apple docs) which determines whether the cancel button is displayed:
self.searchBar.showsCancelButton = false;
But, as per OP comments, this does not work, because the searchController keeps switching the cancel button back on. To avoid this, create a subclass of UISearchBar, and override the setShowsCancelButton methods:
#implementation MySearchBar
-(void)setShowsCancelButton:(BOOL)showsCancelButton {
// Do nothing...
}
-(void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated {
// Do nothing....
}
#end
To ensure this subclass is used by the searchController, we also need to subclass UISearchController, and override the searchBar method to return an instance of our subclass. We also need to ensure that the new searchBar activates the searchController - I've chosen to use the UISearchBarDelegate method textDidChange for this:
#interface MySearchController () <UISearchBarDelegate> {
UISearchBar *_searchBar;
}
#end
#implementation MySearchController
-(UISearchBar *)searchBar {
if (_searchBar == nil) {
_searchBar = [[MySearchBar alloc] initWithFrame:CGRectZero];
_searchBar.delegate = self;
}
return _searchBar;
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ([searchBar.text length] > 0) {
self.active = true;
} else {
self.active = false;
}
}
#end
Finally, change your code to instantiate this subclass:
self.searchController = [[MySearchController alloc] initWithSearchResultsController:self.resultsViewController];
(You will obviously need to import the relevant header files for these subclasses).
Easy solution in Swift3 - we need to make CustomSearchBar without cancel button and then override the corresponding property in new CustomSearchController:
class CustomSearchBar: UISearchBar {
override func setShowsCancelButton(_ showsCancelButton: Bool, animated: Bool) {
super.setShowsCancelButton(false, animated: false)
}}
class CustomSearchController: UISearchController {
lazy var _searchBar: CustomSearchBar = {
[unowned self] in
let customSearchBar = CustomSearchBar(frame: CGRect.zero)
return customSearchBar
}()
override var searchBar: UISearchBar {
get {
return _searchBar
}
}}
In MyViewController I initialize and configure searchController using this new custom subclass:
var videoSearchController: UISearchController = ({
// Display search results in a separate view controller
// let storyBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
// let alternateController = storyBoard.instantiateViewController(withIdentifier: "aTV") as! AlternateTableViewController
// let controller = UISearchController(searchResultsController: alternateController)
let controller = CustomSearchController(searchResultsController: nil)
controller.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. iceland)", comment: "")
controller.hidesNavigationBarDuringPresentation = false
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.searchBarStyle = .minimal
controller.searchBar.sizeToFit()
return controller
})()
And it works properly and smooth
You could do like this:
- (void)willPresentSearchController:(UISearchController *)searchController {
dispatch_async(dispatch_get_main_queue(), ^{
searchController.searchBar.showsCancelButton = NO;
}); }
#pbasdf's answer works for the most part, but checking the searchText length to determine whether the UISearchController is active can add more work to the user. The corner case would be if the user hits the clear button, or deletes the only character in the search bar. This would set active to NO, which would automatically call resignFirstResponder on the UISearchBar. The keyboard would disappear and if the user wants to change the text or enter more text, it would require tapping again on the search bar.
Instead, I only set active to NO if the search bar is not the first responder (keyboard is not active and displayed), since that is effectively a cancel command.
FJSearchBar
Marking searchController.searchBar.showsCancelButton = NO doesn't seem to work in iOS 8. I haven't tested iOS 9.
FJSearchBar.h
Empty, but placed here for completeness.
#import UIKit;
#interface FJSearchBar : UISearchBar
#end
FJSearchBar.m
#import "FJSearchBar.h"
#implementation FJSearchBar
- (void)setShowsCancelButton:(BOOL)showsCancelButton {
// do nothing
}
- (void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated {
// do nothing
}
#end
FJSearchController
Here's where you want to make the real changes. I split the UISearchBarDelegate into its own category because, IMHO, the categories make the classes cleaner and easier to maintain. If you want to keep the delegate within the main class interface/implementation, you're more than welcome to do so.
FJSearchController.h
#import UIKit;
#interface FJSearchController : UISearchController
#end
#interface FJSearchController (UISearchBarDelegate) <UISearchBarDelegate>
#end
FJSearchController.m
#import "FJSearchController.h"
#import "FJSearchBar.h"
#implementation FJSearchController {
#private
FJSearchBar *_searchBar;
BOOL _clearedOutside;
}
- (UISearchBar *)searchBar {
if (_searchBar == nil) {
// if you're not hiding the cancel button, simply uncomment the line below and delete the FJSearchBar alloc/init
// _searchBar = [[UISearchBar alloc] init];
_searchBar = [[FJSearchBar alloc] init];
_searchBar.delegate = self;
}
return _searchBar;
}
#end
#implementation FJSearchController (UISearchBarDelegate)
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
// if we cleared from outside then we should not allow any new editing
BOOL shouldAllowEditing = !_clearedOutside;
_clearedOutside = NO;
return shouldAllowEditing;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
// hide the keyboard since the user will no longer add any more input
[searchBar resignFirstResponder];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (![searchBar isFirstResponder]) {
// the user cleared the search while not in typing mode, so we should deactivate searching
self.active = NO;
_clearedOutside = YES;
return;
}
// update the search results
[self.searchResultsUpdater updateSearchResultsForSearchController:self];
}
#end
Some parts to note:
I've put the search bar and the BOOL as private variables instead of properties because
They're more lightweight than private properties.
They don't need to be seen or modified by the outside world.
We check whether the searchBar is the first responder. If it's not, then we actually deactivate the search controller because the text is empty and we're no longer searching. If you really want to be sure, you can also ensure that searchText.length == 0.
searchBar:textDidChange: is invoked before searchBarShouldBeginEditing:, which is why we handled it in this order.
I update the search results every time the text changes, but you may want to move the [self.searchResultsUpdater updateSearchResultsForSearchController:self]; to searchBarSearchButtonClicked: if you only want the search performed after the user presses the Search button.
I was able to get the UISearchBar to behave as desired without subclassing by calling setShowsCancelButton in a couple of UISearchBarDelegate methods:
I call it in textDidChange and searchBarCancelButtonClicked. Here's what my implementation looks like:
extension MyViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.isEmpty == false {
searchBar.setShowsCancelButton(true, animated: true)
// whatever extra stuff you need to do
} else {
searchBar.setShowsCancelButton(false, animated: true)
// whatever extra stuff you need to do
}
// whatever extra stuff you need to do
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(false, animated: false)
searchBar.text = nil
searchBar.resignFirstResponder()
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
// whatever extra stuff you need to do
}
}
My goal is to prevent the cancel button from appearing in a search bar in a UISearchController. I started with Apple's Table Search with UISearchController sample code and hid the cancel button as seen in the code snip below. However, when the user taps in the text field, the cancel button still appears. Any help?
override func viewDidLoad() {
super.viewDidLoad()
resultsTableController = ResultsTableController()
searchController = UISearchController(searchResultsController: resultsTableController)
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
searchController.searchBar.delegate = self
//Hide cancel button - added by me
searchController.searchBar.showsCancelButton = false
...
I think there are three ways of achieving that:
Override searchDisplayControllerDidBeginSearch and use the following code:
searchController.searchBar.showsCancelButton = false
Subclass UISearchBar and override the layoutSubviews to change that var when the system attempts to draw it.
Register for keyboard notification UIKeyboardWillShowNotification and apply the code in point 1.
Of course can always implement your search bar.
For iOS 8, and UISearchController, use this delegate method from UISearchControllerDelegate:
func didPresentSearchController(searchController: UISearchController) {
searchController.searchBar.showsCancelButton = false
}
Don't forget to set yourself as the delegate: searchController.delegate = self
Simply subclass UISearchController & UISearchBar.
class NoCancelButtonSearchController: UISearchController {
let noCancelButtonSearchBar = NoCancelButtonSearchBar()
override var searchBar: UISearchBar { return noCancelButtonSearchBar }
}
class NoCancelButtonSearchBar: UISearchBar {
override func setShowsCancelButton(_ showsCancelButton: Bool, animated: Bool) { /* void */ }
}
The following github project subclasses UISearchBar which is presented as solution 2:
https://github.com/mechaman/CustomSearchControllerSwift
On top of it, it also subclasses UISearchController to enable one to put the search bar in places other than the tableView header!
Hope this helps.
This was the simplest solution I could come up with in Swift.
Custom search controller:
class CustomSearchController: UISearchController {
var _searchBar: CustomSearchBar
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
self._searchBar = CustomSearchBar()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
override init(searchResultsController: UIViewController?) {
self._searchBar = CustomSearchBar()
super.init(searchResultsController: searchResultsController)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var searchBar: UISearchBar {
return self._searchBar
}
}
Custom search bar:
class CustomSearchBar: UISearchBar {
override func setShowsCancelButton(showsCancelButton: Bool, animated: Bool) {
// do nothing
}
}
The most important piece of this was to only create the _searchBar object once in init vs. creating it inside of the stored property.
Just subclass your UISearchController and do the following:
class CustomSearchController: UISearchController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
searchBar.showsCancelButton = false
}
}
This was the easiest solution I could came up with in order to solve the flashing cancel-button issue.
TL;DR:
Subclassing UISearchBar and overriding setShowsCancelButton: and setShowsCancelButton:animated: hides the cancel button.
SOLUTION
I set active to NO if the search bar is not the first responder (keyboard is not active and displayed), since that is effectively a cancel command.
FJSearchBar
Marking searchController.searchBar.showsCancelButton = NO doesn't seem to work in iOS 8. I haven't tested iOS 9.
FJSearchBar.h
Empty, but placed here for completeness.
#import UIKit;
#interface FJSearchBar : UISearchBar
#end
FJSearchBar.m
#import "FJSearchBar.h"
#implementation FJSearchBar
- (void)setShowsCancelButton:(BOOL)showsCancelButton {
// do nothing
}
- (void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated {
// do nothing
}
#end
FJSearchController
Here's where you want to make the real changes. I split the UISearchBarDelegate into its own category because, IMHO, the categories make the classes cleaner and easier to maintain. If you want to keep the delegate within the main class interface/implementation, you're more than welcome to do so.
FJSearchController.h
#import UIKit;
#interface FJSearchController : UISearchController
#end
#interface FJSearchController (UISearchBarDelegate) <UISearchBarDelegate>
#end
FJSearchController.m
#import "FJSearchController.h"
#import "FJSearchBar.h"
#implementation FJSearchController {
#private
FJSearchBar *_searchBar;
BOOL _clearedOutside;
}
- (UISearchBar *)searchBar {
if (_searchBar == nil) {
// if you're not hiding the cancel button, simply uncomment the line below and delete the FJSearchBar alloc/init
// _searchBar = [[UISearchBar alloc] init];
_searchBar = [[FJSearchBar alloc] init];
_searchBar.delegate = self;
}
return _searchBar;
}
#end
#implementation FJSearchController (UISearchBarDelegate)
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
// if we cleared from outside then we should not allow any new editing
BOOL shouldAllowEditing = !_clearedOutside;
_clearedOutside = NO;
return shouldAllowEditing;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
// hide the keyboard since the user will no longer add any more input
[searchBar resignFirstResponder];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (![searchBar isFirstResponder]) {
// the user cleared the search while not in typing mode, so we should deactivate searching
self.active = NO;
_clearedOutside = YES;
return;
}
// update the search results
[self.searchResultsUpdater updateSearchResultsForSearchController:self];
}
#end
Some parts to note:
I've put the search bar and the BOOL as private variables instead of properties because
They're more lightweight than private properties.
They don't need to be seen or modified by the outside world.
We check whether the searchBar is the first responder. If it's not, then we actually deactivate the search controller because the text is empty and we're no longer searching. If you really want to be sure, you can also ensure that searchText.length == 0.
searchBar:textDidChange: is invoked before searchBarShouldBeginEditing:, which is why we handled it in this order.
I update the search results every time the text changes, but you may want to move the [self.searchResultsUpdater updateSearchResultsForSearchController:self]; to searchBarSearchButtonClicked: if you only want the search performed after the user presses the Search button.
Swift:
The following worked for me, added under viewDidLoad, because I never wanted that button:
let searchBarStyle = searchBar.value(forKey: "searchField") as? UITextField
searchBarStyle?.clearButtonMode = .never
Make sure to add the ID for the searchBar in the storyboard.
Use UISearchControllerDelegate.
func willPresentSearchController(_ searchController: UISearchController) {
searchController.searchBar.setValue("", forKey:"_cancelButtonText")
}
I'm constructing my first App for IOS and I'm struggling to find a way to do a simple ScrollView using the Swift code on the XCode6, please can someone help me to find the solution?
My problem is that I don't know how to make the scrollview work in my code. I already putted the code as you can see below in the ViewController.swift and I was expecting to be able to select the Outlet "scroller" in the Main.storyboard for a ViewController, instead of this I'm receiving the error *"fatal error: Can't unwrap Optional.None (lldb)"* EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)
I have some ViewController screens and in one of that I putted one ScrollView and I want to make it works using the Swift.
I'm stuck on this:
import UIKit
class ViewController: UIViewController {
#IBOutlet var scroller:UIScrollView
override func viewDidLoad() {
super.viewDidLoad()
scroller.scrollEnabled = true;
scroller.contentSize = CGSizeMake(320, 624);
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I think if someone can provide a simple example how to do a scrollview using swift it will solve my problem. Any help is appreciate.
Trying to do it in a old style I tried to do it using a .m and .h file:
ViewController.m
#import "Amigo-Bridging-Header.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[scroller setScrollEnabled:YES];
[scroller setContentSize:CGSizeMake(320, 624)];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Amigo-Bridging-Header.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
IBOutlet UIScrollView *scroller;
}
#end
Cheers
Let's give this a shot. The one thing to note is I have yet to find a way to downcast self.view as a UIScrollView, so you can't make calls like self.view.contentOffset.
import UIKit
class ScrollingViewController : UIViewController {
// Create a scrollView property that we'll set as our view in -loadView
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
override func loadView() {
// calling self.view later on will return a UIView!, but we can simply call
// self.scrollView to adjust properties of the scroll view:
self.view = self.scrollView
// setup the scroll view
self.scrollView.contentSize = CGSize(width:1234, height: 5678)
// etc...
}
func example() {
let sampleSubView = UIView()
self.view.addSubview(sampleSubView) // adds to the scroll view
// cannot do this:
// self.view.contentOffset = CGPoint(x: 10, y: 20)
// so instead we do this:
self.scrollView.contentOffset = CGPoint(x: 10, y: 20)
}
}
Your outlet is not connected. From the Swift with objective-C book:
When you declare an outlet in Swift, the compiler automatically converts the type to a weak implicitly unwrapped optional and assigns it an initial value of nil. In effect, the compiler replaces #IBOutlet var name: Type with #IBOutlet weak var name: Type! = nil
If this value was not connected, it would remain as nil and you'd get a runtime error when accessing the value.
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// setup the scroll view
self.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 200, 0);
}
I created a UIView programmatically and added a UIButton as it's subview.
I want a UIViewController to be the target of that button action.
How would I do that?
If it was created by Interface Builder then it was easy by using IBAction.
If you are adding the button programmatically to a subclass of UIView, then you can do it one of two ways:
You can make the button a property of the view, and then in the viewController that instantiates the view you can set the target of the button as follows:
[viewSubclass.buttonName addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
This will set the button's target to a method of buttonTapped: in the viewController.m
You can create a protocol in your subview, which the parent viewController will conform to. In your view, when you add your button set it to call a method in your view. Then call the delegate method from that view so that your viewController can respond to it:
In the top your view subclass .h create the protocol:
#protocol ButtonProtocolName
- (void)buttonWasPressed;
#end
Create a property for the delegate:
#property (nonatomic, assign) id <ButtonProtocolName> delegate;
In the subclass .m set your button selector:
[button addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
In the buttonTapped: method call the delegate method:
- (void)buttonTapped:(id)sender {
[self.delegate buttonWasPressed];
}
In your viewController.h you'll need to make sure it conforms to the protocol:
#interface someViewController : UIViewController <SomeButtonProtocolName>
In your viewController.m when you init your subview, you'll have to set the delegate:
SomeView *view = ... // Init your view
// Set the delegate
view.delegate = self;
Finally, add the delegate method buttonWasPressed to the viewController.m:
- (void)buttonWasPressed {
// Put code here for button's intended action.
}
Updated to provide Swift example
// Simple delegate protocol.
protocol SomeViewDelegate: class {
// Method used to tell the delegate that the button was pressed in the subview.
// You can add parameters here as you like.
func buttonWasPressed()
}
class SomeView: UIView {
// Define the view's delegate.
weak var delegate: SomeViewDelegate?
// Assuming you already have a button.
var button: UIButton!
// Once your view & button has been initialized, configure the button's target.
func configureButton() {
// Set your target
self.button.addTarget(self, action: #selector(someButtonPressed(_:)), for: .touchUpInside)
}
#objc func someButtonPressed(_ sender: UIButton) {
delegate?.buttonWasPressed()
}
}
// Conform to the delegate protocol
class SomeViewController: UIViewController, SomeViewDelegate {
var someView: SomeView!
func buttonWasPressed() {
// UIViewController can handle SomeView's button press.
}
}
Additionally, here is a quick example using a closure instead of a delegate. (This can approach also be implemented in ObjC using blocks.)
// Use typeAlias to define closure
typealias ButtonPressedHandler = () -> Void
class SomeView: UIView {
// Define the view's delegate.
var pressedHandler: ButtonPressedHandler?
// Assuming you already have a button.
var button: UIButton!
// Once your view & button has been initialized, configure the button's target.
func configureButton() {
// Set your target
self.button.addTarget(self, action: #selector(someButtonPressed(_:)), for: .touchUpInside)
}
#objc func someButtonPressed(_ sender: UIButton) {
pressedHandler?()
}
}
class SomeViewController: UIViewController {
var someView: SomeView!
// Set the closure in the ViewController
func configureButtonHandling() {
someView.pressedHandler = {
// UIViewController can handle SomeView's button press.
}
}
}
You can add target and action to button.
[button addTarget:controller/self action:#selector(onTapButton) forControlEvents:UIControlEventTouchUpInside];
Target is controller so If you want to handle touch event in current controller use self. Other wise you should have a pointer/reference to the controller obj, and use that reference instead of the self.
onTapButton is selector which will be called when user tap on the button. onTapButton do not taking any parameter, If you want to use with parameter use onTapButton:
- (IBAction/void)onTapButton{
}
-(IBAction/void)onTapButton:(id)sender{
}
NOTE:
Better way is to handle this is to use delegation pattern, have target in self what ever class that is, and After that call delegate, and controller should implement that delegate. Keeping direct reference to the controller is not good practice.
We can achieve this without delegates as well.
In your CustomUIView.m class, implement the following method:-
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
NSArray *viewsInNib = [[NSBundle mainBundle]loadNibNamed:#"AddressAlertView" owner:self options:nil];
for (id view in viewsInNib) {
if ([view isKindOfClass:[self class]]) {
self = view;
break;
}
}
return self;
}
Explanation:- In initWithFrame method, I am loading the current nib. Loading the nib means initialising all the sub view which it contains. Get the fully initilised view of same class and assigned to self. Return self which contains fully initilised uiview.
In your viewcontroller.m file, write the below code to add custom uiview and set target of button:-
UIWindow *mainWindow = [[[UIApplication sharedApplication]delegate]window];
AddressAlertView *objAddressAlertView = [[AddressAlertView alloc] initWithFrame:mainWindow.bounds];
[objAddressAlertView.btnCross addTarget:self
action:#selector(dummy)
forControlEvents:UIControlEventTouchUpInside];
if (objAddressAlertView)
[mainWindow addSubview:objAddressAlertView];
[buttonName addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
Now, if you are using UINavigationController your buttonPressed function will be
- (void)buttonPressed:(id)sender
{
NewViewController *newVC = [NewViewController new];
[self.navigationController pushViewController:newVC animated:YES];
}
If you are not using navigation Controller than
- (void)buttonPressed:(id)sender
{
NewViewController *newVC = [NewViewController new];
[self presentViewController:newVC animated:YES completion:nil];
}
For Swift 4
class MyView: UIView {
weak var delegate: MyBtnDelegate?
var myBtn: UIButton = {
let myCustomButton = UIButton()
// Button UI code goes here
myCustomButton.addTarget(self, action: #selector(saveData), for: .touchUpInside)
return myCustomButton
}()
#objc func saveData() {
delegate?.doSomething()
}
In ViewController
protocol MyBtnDelegate: class {
func doSomething()
}
class ViewController: UIViewController, MyBtnDelegate {
var customView = MyView()
override func viewDidLoad() {
super.viewDidLoad()
customView.delegate = self // Unless you add this line code won't be working
}
func doSomething() {
//Do whatever you want
}
}
Hope this helps. Happy coding :]