passing info from collectionView to gameScene - uiviewcontroller

I have created a game using SpriteKit (GameScene and GameViewController) and Objective-C. Everything there works fine.
Within the same App I have created a UIViewController and a second viewController in storyboard that uses CollectionView. I have the collectionView loading the array.
I want to be able to tap on a collectionView Cell and have it open the gameView. Which I can do. However I would like to pass some information from the collectionView to the GameScene so I can load the background image and other information to individualize the GameScene for each collectionViewCell.
Here is my code for the collectionView
#import "collectionViewController.h"
#import "GameScene.h"
#import "GameViewController.h"
#implementation collectionViewController
#synthesize animalArray,theImage,StringPicture,myCell;
-(void) viewDidLoad {
[super viewDidLoad];
animalArray = [NSArray arrayWithObjects:#"Cat.png",
#"image1.png",
#"image2.png",
#"image3.png",
,nil];
}
-(NSInteger) numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
-(NSInteger) collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return animalArray.count;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
myCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
UIImage *images;
long row = [indexPath row];
images = [UIImage imageNamed:animalArray[row]];
myCell.image1.image = images;
return myCell;
}
-(void) collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"indexPath.row:%ld",(long)indexPath.row);
if (indexPath.row==1) {
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"GameSceneOne"];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
NSLog(#"mycell.image1.image:%#",myCell.image1.image);
[self presentViewController:vc animated:YES completion:NULL];
}
if (indexPath.row==2) {
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"GameSceneOne"];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:vc animated:YES completion:NULL];
}
}
I have tried assigning the GameScene.image to the vc.image but one is a GameView and the other is a UIViewController so I can't do that.
I have tried using a segue but still the information can't be passed from on to the other.
How should I do this?

I approached this in a different way. Basically:
Cell has the skview and loads and presents scene.
Scene exposes properties (like a label for the months in the example)
In the cellForItemAt of the collectionView datasource, I instantiate the scene and just use the exposed properties.
Here is in code:
Step 1:
class GameCell: UICollectionViewCell {
#IBOutlet weak var myskview: SKView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
if let scene = SKScene(fileNamed: "MyScene") as? MyScene{
scene.scaleMode = .aspectFill
myskview.presentScene(scene)
}
}
}
Step 2:
class MyScene: SKScene {
var gameLabel: SKLabelNode!
var monthValue: String?
var bird: SKSpriteNode?
var floor: SKSpriteNode?
var bgColor: UIColor?
let birdCategory: UInt32 = 0x1 << 0
let floorCategory: UInt32 = 0x1 << 1
override func didMove(to view: SKView) { }
Step 3:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GameCell", for: indexPath) as! GameCell
let scene = cell.myskview.scene as! MyScene
scene.gameLabel.text = monthComponents[indexPath.row]
Just don't forget to create the label inside the scene using:
gameLabel = scene?.childNode(withName: "gameLabel") as! SKLabelNode
You can check the final result in my git: https://github.com/tennydesign/spriteKitAndCollectionView
Just pardon my dust. =)

Couple of ways to do this. One of them is using NSUserDefaults.
To store data in NSUserDefaults, do this:
// object can be almost anything. Strings, arrays, dictionaries...
NSString *object = #"WellDone";
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:object forKey:#"Hamburger"];
[defaults synchronize];
To read NSUserDefaults, do this:
NSString *myString = [[NSUserDefaults standardUserDefaults] objectForKey:#"Hamburger"];
The key being that you use the same key as when you created it.
And of course, read the docs (blah,blah, blah).

Related

Share JSON Data in TabBarController to view controllers

I am using a tabbarcontroller to show 3 xib's. I would like to decode JSON data in the UITabBarController subclass, and then share the data with the view controllers (as I understand that is the preferred way to do this). I had already successfully accomplished this individually in each view controller, where the same JSON data was getting decoded separately 3 times, but I am now trying to make the process more efficient by only dealing with JSON once.
I am currently getting the following error
"Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee7ab7d98)".
Below is the code I am currently using. I'm mostly only including the code for the first view controller, but it is the same for the others
Here is one of the view controllers. Any help would be appreciated, thank you!
class FirstCollectionViewController: UIViewController {
var tbvc = CustomTabBar()
var statisticsData = [Model]()
let firstCellIdentifier = "FirstCellIdentifier"
#IBOutlet weak var FirstCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
FirstCollectionView.delegate = self
FirstCollectionView.dataSource = self
FirstCollectionView.register(UINib(nibName: "FirstCollectionViewCell", bundle: nil),forCellWithReuseIdentifier: firstCellIdentifier)
}
}
Here is the subclasses UITabBarController
import UIKit
class CustomTabBar: UITabBarController {
let website = "https:......."
var statisticsData = [Model]()
override func viewDidLoad() {
super.viewDidLoad()
let firstTab = FirstCollectionViewController(nibName: "FirstCollectionViewController", bundle: nil)
let secondTab = SecondCollectionViewController(nibName: "SecondCollectionViewController", bundle: nil)
let thirdTab = ThirdCollectionViewController(nibName: "ThirdCollectionViewController", bundle: nil)
viewControllers = [firstTab, secondTab, thirdTab]
downloadJSON(website: website) {
firstTab.statisticsData = self.statisticsData
secondTab.statisticsData = self.statisticsData
thirdTab.statisticsData = self.statisticsData
firstTab.FirstCollectionView.reloadData()
secondTab.SecondCollectionView.reloadData()
thirdTab.ThirdCollectionView.reloadData()
}
}
func downloadJSON(website:String, completed:#escaping ()->()){
guard let qurl = URL(string: website) else { return }
URLSession.shared.dataTask(with: qurl) { (data, response, error) in
if error == nil {
do{
self.statisticsData = try JSONDecoder().decode([Model].self, from: data!)
DispatchQueue.main.async{
completed()
}
} catch {
print("JSON Error")
}}
}.resume()
}
}
Once the data is loaded, you should assign the data to the viewControllers that are added in the tabBarController's Child list as below,
downloadJSON(website: website) {
firstTab.statisticsData = self.statisticsData
secondTab.statisticsData = self.statisticsData
thirdTab.statisticsData = self.statisticsData
firstTab.FirstCollectionView.reloadData()
secondTab.SecondCollectionView.reloadData()
thirdTab.ThirdCollectionView.reloadData()
}
You can also remove the below lines from viewDidLoad of FirstCollectionViewController, SecondCollectionViewController and ThirdCollectionViewController
tbvc = tabBarController as! CustomTabBar
statisticsData = tbvc.statisticsData

Optional(<OS_dispatch_data: data[0x7f97b145f2f0] = { leaf, size = 17290, buf = 0x11a63f000 }>)

Trying to consume web service in iOS using JSON but end up with the above error in the last.. not sure why that's happening..
// Just Convert NSData to String
NSString *String = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"responseObject:%#",String);
Try correcting your do and catch..
import UIKit
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var lbl_studentID: UILabel!
#IBOutlet weak var image: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// 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.
}
#IBAction func btnfunc_getStudentID(sender: UIButton) {
let data:String = "http://neo4j.nannasgroup.com/service.php"
let url:NSURL = NSURL.init(string: data)!
let req:NSURLRequest = NSURLRequest.init(URL: url)
let res:AutoreleasingUnsafeMutablePointer<NSURLResponse?>=nil
do {
let dataVal = try NSURLConnection.sendSynchronousRequest(req, returningResponse: res)
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(dataVal, options: []) as? NSMutableArray {
lbl_studentID.text = "\(jsonResult)"
}
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print("incatch" + error.localizedDescription)
}
}
#IBAction func selectPicture(sender: UIButton) {
let ImagePicker = UIImagePickerController()
ImagePicker.delegate = self
ImagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(ImagePicker, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
image.image = info[UIImagePickerControllerOriginalImage] as? UIImage
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func upload_request(sender: UIButton) {
}
}
I got the same issue OS_dispatch_data in Objective C when I tried to make a post request to the API and got some response. When I tried to print data that I received from API it printed OS_dispatch_data: data[0x2813d9800] = { leaf, size = 442, buf = 0x103825200 }.
This was due to some error with the data from API side.
Explanation With Example:-
I was working on 3DES Encryption and Decryption when I encountered this issue. I was unable to use the data received from API for decryption which was encrypted at the server side.
Decryption Code:
-(NSString *)tripleDesDecryptData:(NSData *)input
key:(NSString *)key
error:(NSError **)error
{
NSParameterAssert(input);
NSParameterAssert(key);
NSData *inputData = input;
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
size_t outLength;
NSAssert(keyData.length == kCCKeySize3DES, #"the keyData is an invalid size");
NSMutableData *outputData = [NSMutableData dataWithLength:(inputData.length + kCCBlockSize3DES)];
CCCryptorStatus
result = CCCrypt(kCCDecrypt, // operation
kCCAlgorithm3DES, // Algorithm
kCCOptionPKCS7Padding | kCCOptionECBMode, // options
keyData.bytes, // key
keyData.length, // keylength
nil,// iv
inputData.bytes, // dataIn
inputData.length, // dataInLength,
outputData.mutableBytes, // dataOut
outputData.length, // dataOutAvailable
&outLength); // dataOutMoved
if (result != kCCSuccess) {
if (error != NULL) {
*error = [NSError errorWithDomain:#"com.your_domain.your_project_name.your_class_name."
code:result
userInfo:nil];
}
return nil;
}
[outputData setLength:outLength];
NSString *tempString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
return tempString;
}
The data I received was encrypted JSON String. Since API returned base64 string, I could not use data directly for decryption in the above method (it failed). I first converted the data to string using -
NSString *jsonStringBase64 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
Then I reconverted back to data that could be used in the above method using -
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:jsonStringBase64 options:0];
The decoded data was null. It was unable to convert back to data because the data was inaccurate or corrupt.
Then I changed options value from 0 to NSDataBase64DecodingIgnoreUnknownCharacters in the above code -
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:plainText options:NSDataBase64DecodingIgnoreUnknownCharacters];
Using NSDataBase64DecodingIgnoreUnknownCharacters solved my problem as it ignored the Unknown Characters.
From Apple:- Use the NSDataBase64DecodingIgnoreUnknownCharacters option to modify the decoding algorithm so that it ignores unknown non-Base64 bytes, including line ending characters.

Access to tabbar Controller from AppDelegate

I have this situation:
How can I access the 3rd Tab bar badge from AppDelegate?
I've used this code:
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
UIViewController *vc = [[UIViewController alloc] init];
if ([nonLette isEqualToString:#"0"]) {
for (vc in tabBarController.viewControllers) {
if (vc.tabBarItem.tag == 999) {
vc.tabBarItem.badgeValue = nil;
}
}
but the self.window.rootViewController returns "start View" and so does not work.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabController = storyboard.instantiateViewController(withIdentifier: "TabBarViewController") as! TabBarViewController
let tabArray = tabController.tabBar.items as NSArray!
let alertTabItem = tabArray?.object(at: 3) as! UITabBarItem
alertTabItem.badgeValue = "10"
Ok, I'm resolved, but in part!!!
This method works well. With debug, I can see that now I can change the badge on desired tab, but in reality don't change in graphics (the red number not appear).
Some suggestion?
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UITabBarController *myTabBarController = [storyboard instantiateViewControllerWithIdentifier:#"tabBarController"];
if ([nonLette isEqualToString:#"0"]) {
for (UIViewController *vc in myTabBarController.viewControllers) {
if (vc.tabBarItem.tag == 999) {
vc.tabBarItem.badgeValue = nil;
}
}
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
} else {
for (UIViewController *vc in myTabBarController.viewControllers) {
if (vc.tabBarItem.tag == 999) {
vc.tabBarItem.badgeValue = nonLette;
}
}
[UIApplication sharedApplication].applicationIconBadgeNumber=[result integerValue];
}
With the new XCode 11 and Swift 5 this is no longer possible.
Instead you want to do this in SceneDelegate.swift, in particular this function func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
You then want to do something like this.
self.window = self.window ?? UIWindow()//#JA- If this scene's self.window is nil then set a new UIWindow object to it.
//#Grab the storyboard and ensure that the tab bar controller is reinstantiated with the details below.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "tabBarController") as! UITabBarController
self.window!.rootViewController = tabBarController //Set the rootViewController to our modified version with the StateController instances
self.window!.makeKeyAndVisible()
print("Finished scene setting code")
guard let _ = (scene as? UIWindowScene) else { return }
Hope this helps for the people using the newer XCode out there. The key here is appDelegate no longer has access to window, the sceneDelegate does. If you try to use appDelegate you will get an error that window no longer exists.
Make sure the identifier is set for the tabBarController in the storyboard so it can find it. You can name it whatever, but just refer to that name in the code here for the withIdentifier function

How do you make a UITableView smaller programmatically?

Rather than bloat and convolute this post with what I've tried and failed at, I'll just keep it simple, as I'm sure the answer is probably simpler than I think.
I have a scrolling UITableView on the main view of my application. All I'm trying to do is move the default position--or "starting point"--of the scrolling UITableView down about 194 points to make room for my navigation bar and a couple other UI elements. How do I do this? Here are what I believe to be the pertinent method implementations from my ViewController .h and .m files, respectively:
// MGViewController.h
// UITVPractice
#import <Foundation/Foundation.h>
#interface ItemsViewController : UITableViewController
-(id) init;
-(id) initWithStyle:(UITableViewStyle)style;
#end
// MGViewController.m
// UITVPractice
#import "ItemsViewController.h"
#import "MGItem.h"
#import "MGItemStore.h"
#implementation ItemsViewController
-(void)viewDidLoad {
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"background.png"]];
self.tableView.backgroundView = backgroundImageView;
[super viewDidLoad];
}
-(id) init {
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
/* create 5 random MGItems and place in the MGItemStore */
for (int i = 0; i < 20; i++) {
[[MGItemStore sharedStore] createItem];
}
}
return self;
}
-(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [[[MGItemStore sharedStore] allItems] count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
/* Create an instance of UITableViewCell */
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCell"];
}
UIView *backView = [[UIView alloc] initWithFrame:CGRectZero];
backView.backgroundColor = [UIColor clearColor];
cell.backgroundView = backView;
/* Display custom background image for cell(s) */
cell.backgroundView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"cellBackground.png"] stretchableImageWithLeftCapWidth:0.0 topCapHeight:5.0]];
/* Display custom background image for selected cell(s) */
cell.selectedBackgroundView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"cellBackgroundTouched.png"] stretchableImageWithLeftCapWidth:0.0 topCapHeight:5.0]];
/* eliminate the white box that bounds the black text. */
[[cell contentView] setBackgroundColor:[UIColor clearColor]];
[[cell backgroundView] setBackgroundColor:[UIColor clearColor]];
[cell setBackgroundColor:[UIColor clearColor]];
/* Set the text of the cell to the description of the item that is at the nth index of items, where n = row this cell will appear in on the tableView */
MGItem *p = [[[MGItemStore sharedStore] allItems] objectAtIndex:[indexPath row]];
[[cell textLabel] setText:[p description]];
[[cell textLabel] setTextColor:[UIColor whiteColor]];
// [[cell textLabel] highlightedTextColor: [UIColor purpleColor]];
return cell;
}
-(id) initWithStyle:(UITableViewStyle)style {
return [self init];
}
#end
I apologize if this post comes off as a "do this for me" post, but I've tried about a dozen different things and none of them have worked. Been stuck on this for about 3 days. Thanks for any help you can provide.
EDIT: Yes, Ismael, you're correct. It is a subclass of UITableViewController. I think I understand what you're saying. Working on it now. Thanks to both who answered.
The best way to do this is to "nest" the tableview controller inside another UIView. If you are using storyboards, just add a "container view" then set that view controller's class to that of your tableview controller. Then, changing the container view's size will automatically change the table view's size. Here's an example where I have a few table views nested in one view:
I take it your controller is a subclass of UITableViewController? If that's the case, then you will have to change that.
In order to do this, change the subclass to UIViewController, add the UITableViewDelegate and UITableViewDataSource protocols.
Then, add a UITableView *tableView property and change your viewDidLoad method to look like this:
-(void)viewDidLoad {
[super viewDidLoad]; // [super viewDidLoad]; should go first!!
CGFloat startingPoint = 194.0; // or whatever
CGRect tableRect = self.view.bounds;
tableRect.origin.y = startingPoint;
tableRect.size.height -= startingPoint;
self.tableView = [[UITableView alloc] initWithFrame:tableRect style:UITableViewStyleGrouped]; // or plain, whichever you need
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; // do this if grouped, looks better!
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.tableView];
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"background.png"]];
self.tableView.backgroundView = backgroundImageView;
}
Edit: There's a reason for this. In a normal UIViewController, the self.view is a UIView, but in a UITableViewController, the self.view is the same as self.tableView and is a UITableView, and that's why you can't change the frame of it.

How can I use a html link in UIWebView to push a view on to the stack?

I have a UIWebView containing html-formatted text. In the text some words are links. When clicked on they should push another view on to the stack. How should I write the html link and corresponding objective-c code to make this work?
Set up delegate for UIWebView, then you can handle link action clicked:
-(BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType {
if ( inType == UIWebViewNavigationTypeLinkClicked ) {
[self.navigationController pushViewController:vc animated:YES];
return NO;
}
return YES;
}
Swift 3: Full UIWebView example to push a view in stack and open another html file in bundle when clicked on html link e.g. imprint
class WebViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
var filename:String?
override func viewDidLoad() {
super.viewDidLoad()
guard filename != nil else {
fatalError("filename not defined")
}
view.backgroundColor = UIColor.white
webView.isOpaque = false;
webView.delegate = self
webView.backgroundColor = UIColor.clear
//remove file extension first
filename = filename!.replace(".html", replacement: "")
let localfilePath = Bundle.main.url(forResource: filename, withExtension: "html")
let myRequest = NSURLRequest(url: localfilePath!)
webView.loadRequest(myRequest as URLRequest)
}
...
}
extension WebViewController: UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
//set view title from html document
let pageTitle = webView.stringByEvaluatingJavaScript(from: "document.title")
navigationItem.title = pageTitle
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if navigationType == .linkClicked,
let urlStr = request.url?.absoluteString,
!urlStr.contains("http://"),
let filename = request.url?.lastPathComponent, //e.g. imprint.html
let vc = storyboard?.instantiateViewController(withIdentifier: "WebView") as? WebViewController{
vc.filename = filename
self.navigationController?.pushViewController(vc, animated: true)
return false
}
return true
}
}