I'm working on an iOS app and there's one small bug. My project contains the following:
Custom searchbar on top of a mapView (HomeViewController.swift)
View with a tableview which displays the searchresults (LocationSearchTable.swift)
When searching I add the view with the tableview on top of my mapview and behind my searchbar. This all works fine.
But now when I select a searchresult from the tableView it crashes because it says that my mapView is nil.
I'm having the following in HomeViewController.swift:
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
mapView.delegate = self
}
Adding the LocationSearchTable to the mapView like this:
func didChangeSearchText(searchText: String) {
if (searchText == "" ) {
self.locationSearchTable.view.removeFromSuperview()
} else {
self.view.insertSubview(self.locationSearchTable.view, aboveSubview: mapView)
}
}
And on the LocationSearchTable.swift I want to call the mapView when tapping on something in the list:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let mapView = HomeViewController().mapView
print(mapView)
}
When trying to access the mapView from there it's nil. Why is that? And what am I missing here?
Kind regards,
Wouter
You creating a new instance of HomeViewController and then asking for its mapView before the method awakeFromNib has occurred. The mapView at that point will be nil until the HomeViewController controller is added to a screen
The best way to handle this if I'm reading your hierarchy right is to delegate back to the view with the map from you tableview.
Or you can pass a reference to mapView into The tableView
Related
I have a GMSMapView in my app. The app with an open map screen takes 150 mb in memory.
At some point I add a lot of polygons. After that the application takes 230 mb.
After calling the method [GMSMapView clear] all polygons disappear, but app still takes 230 mb in memory.
Pointers to Polygons are not stored anywhere else.
How to make the map clear the memory an why it does not happen after calling 'clear' method?
Well, this is pain in a**... I not found the official docs.
My solution:
Swift 3.0
1) Do not use Storyboard, create mapView in code:
#IBOutlet weak fileprivate var mapViewContainer: UIView!
weak fileprivate var mapView: GMSMapView? {
didSet {
//you can config and customise your map here
self.configureMapView()
}
}
//If you use clustering
fileprivate var clusterManager: ClusterManager?
fileprivate var clusterRenderer: ClusterRenderer?
2) Create viewController configure function & call it before viewDidLoad:
func configure() {
mapView = GMSMapView()
mapView?.delegate = self
}
3) Layout & add mapView to your UI:
override func viewDidLoad() {
super.viewDidLoad()
if let mapView = mapView {
mapViewContainer.addSubview(mapView)
//Create constraints as you wish
mapView.fillView()
mapViewContainer.layoutSubviews()
}
}
4) Now create function:
func releaseMemory() {
if
let mapView = mapView,
let clusterManager = clusterManager {
clusterManager.clearItems()
mapView.clear()
mapView.delegate = nil
mapView.removeFromSuperview()
}
clusterManager = nil
clusterRenderer = nil
mapView = nil
}
You call it in ViewDidDisappear, or call delegate...
releaseMemory() function clear memory, not so smart, but it works.
You might try storing the polygon objects somewhere and then call polygon.map = nil on all of them, delete the polygon references and then call the map view clear
I was reading the thread: IBOutlet of another view controller is nil
I have a problem very similar to that.
RequestViewController
class DenunciasResueltasViewController: UIViewController {
#IBOutlet var mapView: GMSMapView!
var solicitudes = [SolicitudesModel]()
var tempMap: GMSMapView!
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: 3.4824182, longitude: -8.1776567, zoom: 15)
self.mapView.camera = camera
}
func recenterMap(latitude:Float!, longitude:Float!) -> Void {
let coordinates = CLLocationCoordinate2DMake(CLLocationDegrees(latitude), CLLocationDegrees(longitude))
mapView = tempMap
self.mapView.animate(toLocation: coordinates)
}
RequestTableViewController
class RequestTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Some code to fill the table
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "RequestVC") as! RequestViewController
viewController.recenterMap(latitude: solicitudes[indexPath.row].getLatitude(), longitude: solicitudes[indexPath.row].getLongitude() )
return cell
}
}
Both components are initialized at same time in runtime, I mean, both are part of the same View.
And when the users click in a 'row' I wanna update the mapView
for that resason, I'm using the method 'RecenterMap'
But, the variable 'self.mapView' is always 'nil'.
How I can update this value?
You should use the delegate method didSelectRowAtIndexPath to do the recentering, because this is called when the user tapps on a cell. It could be that the Outlets of the first viewcontroller haven't yet been set when the second starts to fill its cells.
This is a common problem, wanting to access properties of a currently offscreen vc before you are going to present it.
You can solve it by forcing the view hierarchy to be build by accessing its view:
let _ = viewController.view
After this you are free to access any view-related property on the viewcontroller. So you should put it before you call .recenterMap
It's not really clear what are you trying to do: your code seem to instantiate a view controller with map for each table cell, but you don't actually present it after being instantiated.
You instantiate the RequestViewController, but it doesn't appear in the view hierarchy, thus the IBOutlet properties are not instantiated at that moment when you call them. Usually calling a view on a UIViewController instantiates all the outlets on it.
Alternatively, you could call self.present(requestViewController) in order for it to be presented.
What i understood is that, you have both the RequestViewController and RequestTableViewController, both are subview of a parent View,
1- your center on map function needs to be called on the delegate method :
didSelectRowAtIndexPath:
2- You need a reference to the RequestViewController and DO NOT INSTANTIATE IT FROM THE STORYBOARD. as you will get a new instance and not the one that is displayed already.
I have a tap gesture on a UIImageView within a class that extends UITableViewCell. This code should work, I don't see why it doesn't. The only thing I am iffy on is what the "target" should be - should it be the profileImage, or the overall ViewController that things are in?
#IBOutlet weak var profileImage: UIImageView!
var vc: TweetsViewController? = nil
override func awakeFromNib() {
super.awakeFromNib()
let tapGester = UITapGestureRecognizer(target: vc, action: Selector("handleTapGester:"))
tapGester.delegate = self
profileImage.addGestureRecognizer(tapGester)
}
func handleTapGester(tapGesture: UITapGestureRecognizer) {
print("*******hi*******")
vc?.performSegueWithIdentifier("showProfile", sender: nil)
}
And for the record, as this may seem like a relevant error, I initialize vc when the table cell loads.
The target should be the object that will handle the tap gesture and the handleTapGester function should be inside the object class you specified as the target, not inside the UITableViewCell subclass.
You also need to enable user interaction on the UIImageView by saying:
imageView.userInteractionEnabled = true
Why not just add a tap gesture recogniser to the view, then when called query indexPathForRowAtPoint to find out which cell is being tapped?
If you know the cell you can then determine if the UIImage is being tapped and make your call to performSegueWithIdentifier from there.
If it's tapped on a cell that you're not interested in let it fall through and be handled by the table by calling cancelsTouchesInView on the recogniser.
I have a UITableView that contains UITextField in each cell
When I click UITextField in the cell, the keyboard will show and cover my cell. Therefore, I move my cell to the top by using.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
CGPoint scrollPoint = CGPointMake(0, self.activeInputView.frame.origin.y);
[self.tableView setContentOffset:scrollPoint animated:YES];
}
If I use single click on each cell, my application work fine.
However, I use double click on each cell (it mean I tap on it 2 times very quickly), my cell will stop scroll to the top.
In this line
CGPoint scrollPoint = CGPointMake(0, self.activeInputView.frame.origin.y);
can be 2 errors.
Firstly, you have to make sure that inputView that you have captured is the view you want.
Secondly, you should convert fetched point to correct view using appropriate method.
See reference: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/doc/uid/TP40006816-CH3-SW52
For further investigation need more context.
TPKeyboardAvoiding seems to be a solution.
Are you sure that the scrollPoint is correct?
I just created a sample project with a fixed offset and everything works perfectly fine. The table view scrolls upwards after single and double tap on the UITextField of a certain UITableViewCell.
- (void)keyboardDidShow:(NSNotification *)notification {
CGPoint point = CGPointMake(0.0, 30.0);
[self.tableView setContentOffset:point animated:YES];
}
rather then doing code for manually adjusting your tableview frame ,
Let me give you a suggestion, you can use IQKeyboardManager whose pod is available in cocoapods
and it will manage of when clicking on cell's textfield , tableview will automatically scroll.
Try to use third party library IQKeyboardManager it will help you to solve this issue.
https://github.com/hackiftekhar/IQKeyboardManager
Replace following code in your AppDelegate.m file and then you don't need to use scrollview method to handle keyboard :-
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[IQKeyboardManager sharedManager] setEnable:YES];
[self.window makeKeyAndVisible];
return YES;
}
Have you created separate UITableViewCell class ?
I believe as you scroll your UITableView your UI freezes or when you tab twice frequently your UITableView stops responding or taking time to respond.
Here is a working example. You use your scroll view's contentInset and scrollIndicatorInsets properties to avoid the keyboard when it appears. You should register for keyboard notifications. Use the info in the notifications to determine the size of the keyboard--you never know what that will be.
Using content inset is the correct way to handle this for scroll views. If you subsequently need to scroll your editing row into view, use UITableView.scrollToRowAtIndexPath(_:, atScrollPosition:, animated:)
Notice that the does the right thing when the user hides/shows the completions bar.
import UIKit
import CoreGraphics
// our Cell class
class Cell : UITableViewCell
{
// cell reuse identifier for table view
static let Identifier = "Cell"
// the object the cell represents/displays. Could be anything you like
var value:AnyObject? {
didSet {
// update our text field when our cell value is set.
self.textField.text = value as? String
}
}
// use a text field to display our contents, since that allows editing and showing the keyboard
lazy var textField:UITextField = {
let textField = UITextField()
self.contentView.addSubview( textField )
return textField
}()
override func layoutSubviews() {
super.layoutSubviews()
self.textField.frame = contentView.bounds.insetBy(dx: 20, dy: 4 )
}
}
// table view data source class
class DataSource : NSObject, UITableViewDataSource
{
var numberOfRows:Int { return items.count }
let items = [ "Seoul", "São Paulo", "Bombay", "Jakarta", "Karachi", "Moskva", "Istanbul", "Mexico", "Shanghai", "Tokyo", "New" York, "Bangkok", "Beijing", "Delhi", "London", "Hong Kong", "Cairo", "Tehran", "Bogota", "Bandung", "Tianjin", "Lima", "Rio de Janeiro", "Lahore", "Bogor", "Santiago", "St Petersburg", "Shenyang", "Calcutta", "Wuhan" ]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return numberOfRows
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier( Cell.Identifier ) as? Cell ?? Cell()
cell.value = items[ indexPath.row ]
return cell
}
}
class ViewController : UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
// register for notifications when the keyboard appears:
NSNotificationCenter.defaultCenter().addObserver( self, selector: "keyboardWillShow:", name: UIKeyboardWillChangeFrameNotification, object: nil)
}
override func viewDidLayoutSubviews() {
tableView.frame = view.bounds
}
lazy var tableView:UITableView = {
let tableView = UITableView()
self.view.addSubview( tableView )
tableView.dataSource = self.dataSource
tableView.delegate = self
return tableView
}()
lazy var dataSource : DataSource = DataSource()
// Handle keyboard frame changes here.
// Use the CGRect stored in the notification to determine what part of the screen the keyboard will cover.
// Adjust our table view's contentInset and scrollIndicatorInsets properties so that the table view content avoids the part of the screen covered by the keyboard
#objc func keyboardWillShow( note:NSNotification )
{
// read the CGRect from the notification (if any)
if let newFrame = (note.userInfo?[ UIKeyboardFrameEndUserInfoKey ] as? NSValue)?.CGRectValue() {
let insets = UIEdgeInsetsMake( 0, 0, newFrame.height, 0 )
tableView.contentInset = insets
tableView.scrollIndicatorInsets = insets
}
}
}
// need to conform to UITableViewDelegate protocol since we are the table view's delegate
extension ViewController : UITableViewDelegate
{
}
// App set up stuff here:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var window:UIWindow? = UIWindow()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window!.rootViewController = ViewController()
window!.makeKeyAndVisible()
return true
}
}
I have been pulling my hair out trying to get this 'Delegate' thing to work in Swift for an App I am working on.
I have two files: CreateEvent.swift and ContactSelection.swift, where the former calls the latter.
CreateEvent's contents are:
class CreateEventViewController: UIViewController, ContactSelectionDelegate {
/...
var contactSelection: ContactSelectionViewController = ContactSelectionViewController()
override func viewDidLoad() {
super.viewDidLoad()
/...
contactSelection.delegate = self
}
func updateInvitedUsers() {
println("this finally worked")
}
func inviteButton(sender: AnyObject){
invitedLabel.text = "Invite"
invitedLabel.hidden = false
toContactSelection()
}
/...
func toContactSelection() {
let contactSelection = self.storyboard?.instantiateViewControllerWithIdentifier("ContactSelectionViewController") as ContactSelectionViewController
contactSelection.delegate = self
self.navigationController?.pushViewController(contactSelection, animated: true)
}
ContactSelection's contents are:
protocol ContactSelectionDelegate {
func updateInvitedUsers()
}
class ContactSelectionViewController: UITableViewController {
var delegate: ContactSelectionDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.updateInvitedUsers()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Stuff
self.delegate?.updateInvitedUsers()
}
}
What am I doing wrong? I am still new and don't fully understand this subject but after scouring the Internet I can't seem to find an answer. I use the Back button available in the Navigation Bar to return to my CreateEvent view.
var contactSelection: ContactSelectionViewController = ContactSelectionViewController()
This is instantiating a view controller directly, and the value never gets used. Since it looks like you're using storyboards, this isn't a good idea since none of the outlets will be connected and you'll get optional unwrapping crashes. You set the delegate of this view controller but that's irrelevant as it doesn't get used.
It also isn't a good idea because if you do multiple pushes you'll be reusing the same view controller and this will eventually lead to bugs as you'll have leftover state from previous uses which might give you unexpected outcomes. It's better to create a new view controller to push each time.
In your code you're making a brand new contactSelection from the storyboard and pushing it without setting the delegate.
You need to set the delegate on the instance that you're pushing onto the navigation stack.
It's also helpful to pass back a reference in the delegate method which can be used to extract values, rather than relying on a separate reference in the var like you're doing.
So, I'd do the following:
Remove the var contactSelection
Add the delegate before pushing the new contactSelection object
Change the delegate method signature to this:
protocol ContactSelectionDelegate {
func updateInvitedUsers(contactSelection:ContactSelectionViewController)
}
Change your delegate calls to this:
self.delegate?.updateInvitedUsers(self)