how to create xib subview at the center of tableview swift - swift

I have tableview and I created the custom xib uiview as "detailview" for it. I want to show this detailview at the center of scrolled area when tapped to tableview cell. I can show this view but cannot centralized it. when I set value to frame manually, subview will be at center (approximately) but when I tap the cell which is at the bottom, the subview is appearing at the top of page and also it is moving when i scroll the tableview.
Please help me to show this view at the center of the scrolled area and be fixed
Here is my codes;
Detail View :
class TopTenDetailView: UIView {
var screenWidth:CGFloat = UIScreen.mainScreen().bounds.width*0.08
var screenHeight :CGFloat = UIScreen.mainScreen().bounds.height*0.08
class func instanceFromNib() -> UIView {
return UINib(nibName: "TopTenDetail", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius=10
let testFrame : CGRect = CGRectMake(screenWidth,screenHeight,320,480)
self.frame = testFrame
self.userInteractionEnabled=true
}
#IBAction func close(sender: UIButton) {
self.hidden=true
}
}
And TableViewController's method ;
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var detailView = TopTenDetailView.instanceFromNib
self.view.addSubview(detailView())
}

The way this is setup has many problems and i would be surprised if it actually ever works as intended.
A much better, simpler setup uses a OverFullScreen presentation style and it goes like this:
Create a separate UIViewController for your detail view, let's call it DetailViewController use Interface Builder. Make sure to set the background color to CLEAR
Wire up a segue from the "base" UIViewController that holds your UITableView to DetailViewController and give the segue a name. Let's call it 'detailSegue' , basically drag from one view controller to the other. Make sure that you are not dragging from the view but from the yellow icon at the top of the view controller. You are done in Interface Builder.
Ok, now for the code:
// MARK : - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("detailSegue", sender: self)
}
// MARK: - segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? UIViewController{
vc.modalPresentationStyle = .OverFullScreen
}
}
The OverFullScreen presentation style uses a proper UIViewController modal segue but leaves the presenting UIViewController visible under the presented one.
You can then just layout whatever you want on DetailViewController using Interface Builder and autolayout without having to do hacky match calculations on the layout at runtime.
Hope it helps!

Related

TableView disappears after segue unwind

I've spent the whole day trying to figure this out, would like some insight into what's happening here:
I have a custom UITableViewController that's embedded in a navigation controller, and it has a button that triggers a segue to a plain UIViewController.
The plain UIViewController has a button that unwinds to the TableViewController, as well as a "back" button that's given for free thanks to navigation controller.
Now the issue: When I segue from TableViewController to UIViewController, everything works. From UIViewController, if I press the "back" button, it winds back and the TableView is reloaded automatically. HOWEVER, when I press the button that is linked to #IBAction to unwind, it unwinds to the TableViewController but nothing shows up, not even an empty table.
My debug view showed that once I tried to unwind using my button (not "back), TableViewController came back on screen but its frame was super small and nothing was in it, so the black background of the navigation controller was showing, thus just a black screen where the table should be.
Can anyone tell me how to manually reinitialize the tableView or reset the TableViewController when I unwind? I tried tableView.reload() in viewDidAppear() and nothing happened, as I suspect the problem is the TableViewController itself not having the original frame as before segueing.
Link to screenshots with descriptions
TableViewController code:
class StatsTableViewController: UITableViewController {
//variable stuff
#IBAction func unwindToRoot(_ sender: UIStoryboardSegue) {
}
override func viewDidLoad() {
super.viewDidLoad()
self.getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.getData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return solves.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! StatsTableViewCell
cell.timeLabel?.text = solves[indexPath.row].0
cell.scrambleLabel?.text = solves[indexPath.row].1
return cell
}
//other stuff
}
Set Delegate Datasource Methods
//Add this two lines in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
self.getData()
//Add this line
self.UITableViewName.delegate = self
self.UITableViewName.dataSource = self
}
You are using this two methods in statusTableViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SelectionSegue" {
//Your code
}
}
#IBAction func unwindToThisView(sender: UIStoryboardSegue) {
// set Delegate datasource method
}
set outlet for unwindToThisView button to see the images
1.Click exit button for last ViewController
see Presenting Segues outlet for the Back Button
set action for back button

Does the keyboard for a UITextField have to take up the whole screen?

It seems that the keyboard doesn't have to take up the whole screen, check UPDATE in the Question section of my post. Thanks.
Description
Use UITextField to place a full-screen keyboard on the screen.
reference
I've set up a UISplitViewController and I would like the RootViewController (aka MasterViewController) to have the UITextField with the Keyboard showing. Then I would like results of the search on the right (in the "ResultViewController" (UIViewController).
The idea is when the user types, results are proposed.
What I've tried:
I first added a UITextField to my RootViewController via the storyboard but that took up the whole screen when I activated the keyboard via textField.becomeFirstResponder().
I figured if I use a UIAlertController I'd get by this issue, but the keyboard still takes up the whole screen.
class RootViewController: UIViewController {
override func viewDidLoad() {
let alertController = UIAlertController(title: "Search", message: "Search for something!.", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
textField.placeholder = "Search"
})
self.addChildViewController(alertController)
alertController.view.frame = self.view.frame
self.view.addSubview(alertController.view)
}
}
Question:
When using a UISplitViewController how can I get the keyboard to only stay in it's RootViewController and not take up the whole screen?
UPDATE: It appears this has been implemented in the Netflix app on the new apple tv. The keyboard is on the top and takes up the whole width. The bottom is divided into two sections. On the left, proposed word results and on the right a collection view of video covers of possible videos. Everything is updated as the user types.
If this is considered bad design for Apple TV, then feel free to point out why.
Screen shot:
This is what I currently get. The text box opens a keyboard that takes up the whole screen.
Presenting a keyboard only on a portion of the screen is not possible with the built-in SDK methods.
A UIKeyboard is presented on the application's window, not on any of its subviews. To accomplish your desired behavior, use reflection to get a reference to the UIKeyboard object on your application's UIWindow (by iterating through the window's subviews), and change its frame to match the width of your RootViewController.
To get started, you can look at the private UIKeyboard.h tvOS header here. I should note that Apple may have code in place to disable resizing of the keyboard (like in willMoveToWindow: or didMoveToWindow for example). This is undefined behavior and your milage will vary, if it even works at all.
You have deleted your comment on my answer about the Netflix app, but as shirefriendship says in their answer, Netflix probably uses a TVML template. You could go that route and build a hybrid tvOS/TVML app. Additionally, you could also manually build a keyboard yourself with a UICollectionView in your RootViewController.
Netflix is using TVML Templates to implement their search instead of UIKit. You can accomplish the same aesthetic as Netflix using the searchTemplate. You can learn to mix TVML and UIKit here. Unfortunately the searchTemplate is not customizable enough to conform to your desired layout. There is currently no way to implement your specific layout for the Apple TV.
Introduction:
I was finally able to implement this without having to use TVML templates. The final solution looks something like this:
The general idea is to create a UICollectionViewController with a UICollectionViewCell. Then to programmatically add a keyboard and add it to your TabViewController via your AppDelegate.
How to implement this search view with results:
Step 1: Storyboard and Controller creation
Open your storyboard and create a UICollectionViewController (with a custom class "SearchResultViewController") that is not attached to your TabViewController.
Within it create a your UICollectionViewCell with whatever labels and images you want. UICollectionViewCell should have a custom class called "VideoSearchCell".
Your SearchViewController should have nothing else inside.
Step 2: Adding SearchViewController to TabViewController and implementing keyboard via AppDelegate programmatically
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
override init() {
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if let tabController = window?.rootViewController as? UITabBarController {
tabController.viewControllers?.append(configueSearchController())
}
return true
}
//... standard code in-between
func configueSearchController() -> UIViewController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let searchResultController = storyboard.instantiateViewControllerWithIdentifier(SearchResultViewController.storyboardIdentifier) as? SearchResultViewController else {
fatalError("Unable to instatiate a SearchResultViewController from the storyboard.")
}
/*
Create a UISearchController, passing the `searchResultsController` to
use to display search results.
*/
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. Gastric Bypass)", comment: "")
// Contain the `UISearchController` in a `UISearchContainerViewController`.
let searchContainer = UISearchContainerViewController(searchController: searchController)
searchContainer.title = NSLocalizedString("Search", comment: "")
// Finally contain the `UISearchContainerViewController` in a `UINavigationController`.
let searchNavigationController = UINavigationController(rootViewController: searchContainer)
return searchNavigationController
}
}
Once you've added the basic skeleton of your SearchResultViewController you should be able to see the keyboard on the top of the Search view when you run your project.
Step 3: Handling text input and updating results
You'll notice that in my filterString I use a class called ScoreVideo and a StringSearchService. These are just classes I use to filter my Video list (aka: self.vms.videos).
So in the end, just take the filterString, create a new filtered list and reload your collection view.
import UIKit
import Foundation
class SearchResultViewController: UICollectionViewController, UISearchResultsUpdating {
//private let cellComposer = DataItemCellComposer()
private var vms: VideoManagerService!
private var filteredVideos = [ScoreVideo]()
static let storyboardIdentifier = "SearchResultViewController"
var filterString = "" {
didSet {
// Return if the filter string hasn't changed.
guard filterString != oldValue else { return }
// Apply the filter or show all items if the filter string is empty.
if self.filterString.isEmpty {
self.filteredVideos = StringSearchService.start(self.filterString, videos: self.vms.videos)
}
else {
self.filteredVideos = StringSearchService.start(self.filterString, videos: self.vms.videos)
}
self.collectionView?.reloadData()
}
}
override func viewDidLoad() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
self.vms = appDelegate.getVideoManagerService()
}
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("count..\(filteredVideos.count)")
return filteredVideos.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// Dequeue a cell from the collection view.
return collectionView.dequeueReusableCellWithReuseIdentifier(VideoSearchCell.reuseIdentifier, forIndexPath: indexPath)
}
// MARK: UICollectionViewDelegate
override func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
guard let cell = cell as? VideoSearchCell else { fatalError("Expected to display a `VideoSearchCell`.") }
let item = filteredVideos[indexPath.row]
cell.configureCell(item.video)
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
dismissViewControllerAnimated(true, completion: nil)
}
// MARK: UISearchResultsUpdating
func updateSearchResultsForSearchController(searchController: UISearchController) {
print("updating... \(searchController.searchBar.text)")
filterString = searchController.searchBar.text!.lowercaseString ?? ""
}
}
If something is unclear, feel free to ask some questions. I most likely forgot something. Thanks.
Answer inspired from apple sample code

How would I unhide a button if user performs segue in swift?

I have a button inside a cell (PFQueryTableViewController) that is hidden and I want to unhide it when the user performs a certain segue that I call programatically.
When the user taps the cell it segues to a view controller which displays the contents of the cell full screen... I want the button to unhide in this cell when the segue is called so when the user goes back to the table of cells they can see it on the cell they just tapped.
How can I do this?
Edit after questions:
inside cellRowForIndexPath I have the following for the button
cell.myButton.tag = indexPath.row
cell.myButton.addTarget(self, action: "pressed:", forControlEvents: UIControlEvents.TouchUpInside)
cell.myButton.hidden = true
And the segue itself carries information from the cell (stored in Parse backend) across to FullPostViewController from AllPostsTableViewController. The code for that is this (would I call the unhide in here somewhere?):
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showFullPost", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showFullPost" {
let indexPath = self.tableView.indexPathForSelectedRow
let fullPostVC = segue.destinationViewController as! FullPostViewController
let object = self.objectAtIndexPath(indexPath)
fullPostVC.post = object?.objectForKey("postContent") as? String
let likeCount = object!.objectForKey("likedBy")!.count
fullPostVC.likesCounted = String(likeCount)
self.tableView.deselectRowAtIndexPath(indexPath!, animated: true)
}
}
(Answer thoroughly edited after thorough edit of question)
One possible solution follows below.
Since you mention table cells (each containing a button; I'll assume UIButton), I assume you populate your table view cells with UITableViewCell objects; some fancy subclass to the latter. In this class:
If you haven't already, create an #IBOutlet from your button as a property in this class.
Overload the method setSelected(...) to un-hide your button in case the UITableViewCell is selected (which will precede the segue)
Hence, in your UITableViewCell subclass, you should be able to do something along the lines:
// ...TableViewCell.swift
Import UIKit
// ...
class ...TableViewCell: UITableViewCell {
// Properties
#IBOutlet weak var button: UIButton!
// button contained in UITableViewCell
// ...
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// If table cell is selected (after which segue will follow),
// un-hide button.
if (selected) {
button.hidden = false
}
}
}
Hopefully this will achieve your goal.

Fill a textField with a selected TableViewCell?

In my initial view there is a blank textField (with an invisible button over it) that segues to a TableView when you click it. I want to send the text data from my TableViewCell selection to the blank textField in the original view.
I've made a View2.swift file, which is a replica of my original View. I was trying to edit the source code in there to push the textData by sending the .text from the indexPath of the TableView.
This video shows how to send data from a first view to a second view, but I'm trying to send data from my second view (TableView) back to my first view (View).
Here is my code:
ViewController.swift
import UIKit
class ViewController: UITableViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DestViewController : View2 = segue.destinationViewController as! View2
DestViewController.formulaSelectionText = ViewController.
}
}
View2.swift:
import Foundation
import UIKit
class View2: UIViewController {
override func viewDidLoad(){
formulaSelection.text = indexPathForCell(Cell: UITableView)
}
}
I assume it would be best to just update the value in the initial view. I'm thinking with a UITextFieldDelegate?
It is easy to pass back UITableViewCell Selected value back to a controller. Simply on selection of a cell, update the textfield's value in previous controller and pop the controller/dismiss it if you are using navigation controller / modal controller.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//get last controller in hierarchy
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController];
var prevController : PrevViewController = viewControllers[viewControllers.count - 1] as PrevViewController;
prevController.textfield.text = dataSource[indexPath.row]; //update textfield here
self.navigationController!.popViewControllerAnimated(true);
}
Hope it helps!

Master-Detail: UINavigatorController vs Storyboard Segue

Scenario: Master(TableView) --> Detail.
Modus Operandi: Select Row --> display DetailVC
As you can see below, I have a MasterVC embedded in a UINavigationController:
I currently display the DetailVC via pushing it into the UINavigationController's VC stack:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
let storyboard = UIStoryboard(name: "Bliss", bundle: nil);
let controller = storyboard.instantiateViewControllerWithIdentifier("DiaryPlayerVC") as DiaryPlayerViewController
self.navigationController?.pushViewController(controller, animated: true)
}
This works fine.
However, the 'prepareForSeque' doesn't fire:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDiaryPlayer" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as NSDate
// (segue.destinationViewController as DiaryPlayerViewController).detailItem = object
}
}
}
I understand that I probably have two (2) conflicting paradigms here:
1) Using the UINavigationController vs
2) Using the Storyboard Relationship.
So...
Option 1: it appears that I can remove the Segue link to have a storyboard stand-alone DetailVC.
Option 2: via Segue, I'm assuming I can remove the UINavigatorController from the link.
I'm currently using Option #1, launching the DetailVC via the UINavigationController.
Question: If I choose Option #2, how do I access (launch) the DetailVC ("Diary Player") from the Master's Row and hence, fire the Segue's 'prepareForSegue()'?
Answer: create a segue from the table view cell to the detail view controller.
Your screenshot shows that you already created a segue in your storyboard. Give that segue an identifier in its property inspectory. Then you can simply perform the segue in the didSelectRowAtIndexPath method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("tableView didSelectRowAtIndexPath")
performSegueWithIdentifier("mySegueIdentifier", sender: nil)
}
Note: Ctrl-drag the segue from the TableViewController icon, not from the TableViewCell.