I am making an app that will display a random quote from a stoic philosopher. Right now, I am stuck on trying to make the correct picture pop up. (User clicks on a Button with the philosopher's name on it, and then a new view pops up with an image of the philosopher and a random quote by him).
class ViewController: UIViewController {
var allQuotes = [String]()
var pictures = [String]()
#IBOutlet var Epictetus: UIButton!
#IBOutlet var Seneca: UIButton!
#IBOutlet var MarcusAurelius: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create a constant fm and assign it the value returned by FileManager.default (built in system type)
let fm = FileManager.default
// Declares a new constant called path that sets the resource path of ours apps buddle.
// A bundle is a directory containing our compiled program and all our assets
let path = Bundle.main.resourcePath!
// items array will be a constant collection of the names of all the files found in the directory of our app
let items = try! fm.contentsOfDirectory(atPath: path)
// create a loop to go through all of our items...
for item in items {
if item.hasSuffix("jpg"){
pictures.append(item)
}
}
print(pictures)
title = "Stoicism"
if let stoicQuotesURL = Bundle.main.url(forResource: "quotes", withExtension: "txt"){
if let stoicQuotes = try? String(contentsOf: stoicQuotesURL) {
allQuotes = stoicQuotes.components(separatedBy: "\n\n")
}
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
}
That's the code for my main viewController.
import UIKit
class PictureViewController: UIViewController {
#IBOutlet var picture: UIImageView!
#IBOutlet var imageView: UIImageView!
var selectedImage: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let imageToLoad = selectedImage {
imageView.image = UIImage(named: imageToLoad)
}
}
override func viewWillAppear(_ animated: Bool) {
// doing it for the parent class
super.viewWillAppear(animated)
// if its a nav Cont then it will hide bars on tap...
}
// now make sure it turns off when you go back to the main screen
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
That's the code for the viewController that has the imageView. Right now, the image that's popping up is always the preset (Marcus Aurelius), even though my code looks correct to me. Obviously it isn't (also, I've already debugged and ensured through print statements that the jpg files add to the pictures array correctly).
Any help would be appreciated.
First of all, this code is really silly:
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
Do you see that everything in those lines is identical except for the numbers? So make the number a variable:
#IBAction func buttonTapped(_ sender: UIButton) {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
print(sender.tag)
vc.selectedImage = pictures[sender.tag]
navigationController?.pushViewController(vc, animated: true)
}
}
See how much shorter and clearer that is? Okay, I've also added a print statement. This will print the tag to the console. You need to make sure that your buttons do have the right tags. If they do, your code should work.
Related
while using a MockTableView this code still not calling reloadData() from the mock,
please i wanna know what is wrong here.
following this book: Test-Driven IOS Development with Swift 4 - Third Edition
page 164, i was as an exercise
full code repo - on github
ItemListViewController.swift
import UIKit
class ItemListViewController: UIViewController, ItemManagerSettable {
#IBOutlet var tableView: UITableView!
#IBOutlet var dataProvider: (UITableViewDataSource & UITableViewDelegate &
ItemManagerSettable)!
var itemManager: ItemManager?
override func viewDidLoad() {
super.viewDidLoad()
itemManager = ItemManager()
dataProvider.itemManager = itemManager
tableView.dataSource = dataProvider
tableView.delegate = dataProvider
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
#IBAction func addItem(_ sender: UIBarButtonItem) {
if let nextViewController =
storyboard?.instantiateViewController(
withIdentifier: "InputViewController")
as? InputViewController {
nextViewController.itemManager = itemManager
present(nextViewController, animated: true, completion: nil)
}
}
}
ItemListViewControllerTest.swift
import XCTest
#testable import ToDo
class ItemListViewControllerTest: XCTestCase {
var sut: ItemListViewController!
var addButton: UIBarButtonItem!
var action: Selector!
override func setUpWithError() throws {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier:
"ItemListViewController")
sut = vc as? ItemListViewController
addButton = sut.navigationItem.rightBarButtonItem
action = addButton.action
UIApplication.shared.keyWindow?.rootViewController = sut
sut.loadViewIfNeeded()
}
override func tearDownWithError() throws {}
func testItemListVC_ReloadTableViewWhenAddNewTodoItem() {
let mockTableView = MocktableView()
sut.tableView = mockTableView
guard let addButton = sut.navigationItem.rightBarButtonItem else{
XCTFail()
return
}
guard let action = addButton.action else{
XCTFail()
return
}
sut.performSelector(onMainThread: action, with: addButton, waitUntilDone: true)
guard let inputViewController = sut.presentedViewController as?
InputViewController else{
XCTFail()
return
}
inputViewController.titleTextField.text = "Test Title"
inputViewController.save()
XCTAssertTrue(mockTableView.calledReloadData)
}
}
extension ItemListViewControllerTest{
class MocktableView: UITableView{
var calledReloadData: Bool = false
override func reloadData() {
calledReloadData = true
super.reloadData()
}
}
}
You inject a MockTableview Then you call loadViewIfNeeded(). But because this view controller is storyboard-based and the table view is an outlet, the actual table view is loaded at this time. This replaces your MockTableview.
One solution is:
Call loadViewIfNeeded() first
Inject the MockTableview to replace the actual table view
Call viewDidLoad() directly. Even though loadViewIfNeeded() already called it, we need to repeat it now that we have a different tableview in place.
Another possible solution:
Avoid MockTableview completely. Continue to use a real table view. You can test whether it reloads data by checking whether the number of rows matches the changed data.
Yet another solution:
Avoid storyboards. You can do this with plain XIBs (but these lack table view prototype cells) or programmatically.
By the way, I see all your tearDownWithError() implementations are empty. Be sure to tear down everything you set up. Otherwise you will end up with multiple instances of your system under test alive at the same time. I explain there here: https://qualitycoding.org/xctestcase-teardown/
there is a Table View to show phone contact . i want to send phone number and email to another View Controller when Long Pressed the Cell . Long Press Work Correctly But I cant Pass Data to another View Controller .
enter image description here
VC 1 :
override func viewDidLoad() {
super.viewDidLoad()
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longpress))
tbMain.addGestureRecognizer(longPress)
}
Long Press Method for table view cell :
#objc func longpress(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizer.State.began {
let touchPoint = sender.location(in: tbMain)
if tbMain.indexPathForRow(at: touchPoint) != nil {
let cell = tbMain.dequeueReusableCell(withIdentifier: "testCell") as! NewContactCell
print("Long press Pressed:)")
self.actionVC = self.storyboard!.instantiateViewController(withIdentifier: "ActionsViewController") as? ActionsViewController
UIView.transition(with: self.view, duration: 0.25, options: [.transitionCrossDissolve], animations: {
self.view.addSubview( self.actionVC.view)
}, completion: nil)
}
}
}
VC 2 :
internal var strPhoneNUmber : String!
internal var strEmail : String!
override func viewDidLoad() {
super.viewDidLoad()
print("Phone: \(strPhoneNUmber!)")
// Do any additional setup after loading the view.
}
Get phone number and email from cell object
self.actionVC.strPhoneNUmber = cell.strPhoneNUmber // get phone number from cell object
self.actionVC. strEmail = cell.strEmail // get email from cell object
code would be like
#objc func longpress(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizer.State.began {
let touchPoint = sender.location(in: tbMain)
if tbMain.indexPathForRow(at: touchPoint) != nil {
let cell = tbMain.dequeueReusableCell(withIdentifier: "testCell") as! NewContactCell
print("Long press Pressed:)")
self.actionVC = self.storyboard!.instantiateViewController(withIdentifier: "ActionsViewController") as? ActionsViewController
**self.actionVC.strPhoneNUmber = cell.strPhoneNUmber // get phone number from cell object
self.actionVC. strEmail = cell.strEmail // get email from cell object**
UIView.transition(with: self.view, duration: 0.25, options: [.transitionCrossDissolve], animations: {
self.view.addSubview( self.actionVC.view)
}, completion: nil)
}
}
}
I don't see when your trying to pass the data.. you have quite a few way to perform that action first you can use delegation to achieve passing the data
protocol YourDelegate : class {
func passData(phoneNumber: String, email: String)
}
weak var delegate: YourDelegate?
#objc func longpress(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizer.State.began {
let touchPoint = sender.location(in: tbMain)
if tbMain.indexPathForRow(at: touchPoint) != nil {
let cell = tbMain.dequeueReusableCell(withIdentifier: "testCell") as! NewContactCell
print("Long press Pressed:)")
self.actionVC = self.storyboard!.instantiateViewController(withIdentifier: "ActionsViewController") as? ActionsViewController
self.delegate = self
**delegate?.passData(cell.strPhoneNumber, cell.strEmail)**
UIView.transition(with: self.view, duration: 0.25, options: [.transitionCrossDissolve], animations: {
self.view.addSubview( self.actionVC.view)
}, completion: nil)
}
}
}
class ???: UIViewController, YourDelegate {
var strPhoneNUmber : String!
var strEmail : String!
override func viewDidLoad() {
super.viewDidLoad()
print("Phone: \(strPhoneNUmber!)")
// Do any additional setup after loading the view.
}
func passData(phoneNumber: String, email: String) {
handle...
}
}
its not clear to me if the actionVC is the one you want to pass the data to but if so you have an instance.. just set the properties but ill still recommend sticking with the delegation pattern
actionVC.strPhoneNumber = cell.strPhoneNumber
or use a segue
self.performSegue(withIdentifier: "yourIdentifier", sender: arr[indexPath.row])
use prepare for segue to create an instance to set his properties according to the sender..
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destvc = segue.destination as? YourClass
destvc.phoneNumber = sender.phoneNumber as? String
destvc.email = sender.email as? String
}
I would like to pass data from EditPostViewController to NewsfeedTableViewController using delegates, but func remove(mediaItem:_) is never called in the adopting class NewsfeedTableViewController. What am I doing wrong?
NewsfeedTableViewController: UITableViewController, EditPostViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//set ourselves as the delegate
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
}
//remove the row so that we can load a new one with the updated data
func remove(mediaItem: Media) {
print("media is received heeeee")
// it does't print anything
}
}
extension NewsfeedTableViewController {
//when edit button is touched, send the corresponding Media to EditPostViewController
func editPost(cell: MediaTableViewCell) {
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as? EditPostViewController
guard let indexPath = tableView.indexPath(for: cell) else {
print("indexpath was not received")
return}
editPostVC?.currentUser = currentUser
editPostVC?.mediaReceived = cell.mediaObject
self.navigationController?.pushViewController(editPostVC!, animated: true)
}
protocol EditPostViewControllerDelegate: class {
func remove(mediaItem: Media)
}
class EditPostViewController: UITableViewController {
weak var delegate: EditPostViewControllerDelegate?
#IBAction func uploadDidTap(_ sender: Any) {
let mediaReceived = Media()
delegate?.remove(mediaItem: mediaReceived)
}
}
The objects instantiating in viewDidLoad(:) and on edit button click event are not the same objects. Make a variable
var editPostVC: EditPostViewController?
instantiate in in viewDidLoad(:) with delegate
editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
and then present it on click event
navigationController?.pushViewController(editPostVC, animated: true)
or
present(editPostVC, animated: true, completion: nil)
you can pass data from presenter to presented VC before or after presenting the VC.
editPostVC.data = self.data
I suggest having a property in NewsfeedTableViewController
var editPostViewController: EditPostViewController?
and then assigning to that when you instantiate the EditPostViewController.
The idea is that it stops the class being autoreleased when NewsfeedTableViewController.viewDidLoad returns.
I have a collection view with some cells representing a contact (their data has a phone number and name) and I am trying to add the contact to the iPhone contacts. I have created a segue from a button called "add contact" that is inside the CollectionViewCell to a navigation controller, and set its identifier as "ADD_CONTACT".
In the storyboard, my segue has a navigation controller with no root view controller.
in prepareToSegue of the view controller that delegates my UICollectionView I wrote this code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == ADD_CONTACT {
let dest = segue.destination as! UINavigationController
if let cell = sender as? SBInstructionCell {
if cell.isContact {
let newContact = CNMutableContact()
if let phone = cell.instructionBean?.contactAttachment?.phoneNumber{
newContact.phoneNumbers.append(CNLabeledValue(label: "home", value: CNPhoneNumber(stringValue: phone)))
}
if let name = cell.instructionBean?.contactAttachment?.contactName {
newContact.givenName.append(name)
}
let contactVC = CNContactViewController(forNewContact: newContact)
contactVC.contactStore = CNContactStore()
contactVC.delegate = self
dest.setViewControllers([contactVC], animated: false)
}
}
}
}
this results with a black screen.
How can this be fixed? I want to see the CNContactViewController
Eventually I solved this in a different approach using Closures.
In my UICollectionViewCell
I added this var:
var closureForContact: (()->())? = nil
Now on my button's action in the same cell I have this func:
#IBAction func addContactTapped(_ sender: UIButton) {
if closureForContact != nil{
closureForContact!()
}
}
Which calls the function.
In my CollectionView in cell for item at index path, I set the closure like this:
cell.closureForContact = {
if cell.isContact {
let newContact = CNMutableContact()
if let phone = cell.instructionBean?.contactAttachment?.phoneNumber{
newContact.phoneNumbers.append(CNLabeledValue(label: "home", value: CNPhoneNumber(stringValue: phone)))
}
if let name = cell.instructionBean?.contactAttachment?.contactName {
newContact.givenName.append(name)
}
let contactVC = CNContactViewController(forNewContact: newContact)
contactVC.contactStore = CNContactStore()
contactVC.delegate = self
contactVC.allowsEditing = true
contactVC.allowsActions = true
if let nav = self.navigationController {
nav.navigationBar.isTranslucent = false
nav.pushViewController(contactVC, animated: true)
}
}
}
This worked perfectly. I learned that for navigating from a cell, it is best to use closures.
I'm trying to take an image in my app so I can save it to my device and pass it to the next view controller to be previewed. The way I see people doing this is storing the image they take in a uiimage. Then during prepareforsegue they set the uiimage variable in the destination view controller to the photo you took in the previous view controller. From there in the dest view controller I see people displaying the image as follows : imageName.image = imageVariable . When I pass the variable to the destination view controller and try to display it in the next view controller it appears as a nil value. Where am I going wrong?
First ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ToDetailPage" {
let nextScene = segue.destination as! PostDetailPageViewController
nextScene.itemImage = self.image
// nextScene?.myimg.image = self.image
}
}
#IBAction func TakePhotoButtonClicked(_ sender: AnyObject) {
if let videoConnection = sessionOutput.connection(withMediaType: AVMediaTypeVideo){
sessionOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: {
buffer, error in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
self.image = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(UIImage(data: imageData!)!, nil, nil, nil)
})
}
}
Second ViewController:
var itemImage: UIImage!
#IBOutlet weak var myimg: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.categories.dataSource = self;
self.categories.delegate = self;
setUpMap()
myimg.image = itemImage
}
You need to push viewController inside the block. Actually what is happening in this code the completion block is called after prepareForSegue. So your image is always 'nil'.
Try to push the viewController like this:
if let videoConnection = sessionOutput.connection(withMediaType: AVMediaTypeVideo){
sessionOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: {
buffer, error in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
self.image = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(UIImage(data: imageData!)!, nil, nil, nil)
// push view controller here
let destinationVC = SecondViewController()
destinationVC.image = self.image
self.navigationController.pushViewController(destinationVC, animated: true)
})
}
Hope it will help you.. Happy Coding!!