dear objectiveC masters,
I'm a newbie apple programmer and since I'm currently taking a master degree in math education, I want the make a thesis on making apps that based on math education..
Right now, I'm trying to make an iPad app that draws a sine function and then transforms the sine function. I draw the sine graph by overriding the drawrect function in a custom uiview class and load it to a uiview object. The sine function is drawn nicely on a context, along with grids and axes that are drawn on different contexts.
I put several sliders and then plan to use the sliders to change the variables in the uiview class that i use for drawing. Now here's the problem, I realize that I can't access the variables in the viewcontroller from the custom uiview class, and I suspect that I may have mistakenly used the wrong paradigm in writing the whole program.
Can someone help me to clear the confusion here? It doesn't have to be in the exact codes but more to the big pictures of how I should draw and redraw the sine function on a view object whilst changing the variables of the sine function through sliders.
Thank you for your kind help :)
Chandra from Indonesia.
There are two ways to solve this:
Instead of letting the UIView ask the values from the UIViewController, you can also push the values to the UIVIew the moment one of the sliders changes. This way the UIView does what it should do: draw what the ViewController asks him to.
Think of a function like redrawUsingNewValues: that you implement in the UIView and you call from the UIViewController.
Use delegation. If you really want the UIView to be in control, you can give it a pointer to the UIViewController using delegation. That way the UIView doesn't own the UIViewController, but you can get the values you want.
An introduction on delegation can be found here: Delegation and the Cocoa Frameworks
Good luck on your program!
A method in the UIViewController should recognize when the slider values changes
the same method should trigger another methode within the UIViewController to update/recalculate the sine function values (e.g. creating an value array)
at the end of the update-methode the necessary values are transferred to the UIView via an outlet from UIViewController to the UIView (the UIView is an property of the UIViewController)
the UIView is drawing the new sine-function in draw rect
Edit 1:
your ViewController.h:
#import <UIKit/UIKit.h>
#class YourGraphUIView; // that's you view where you draw
#interface ResultViewController: UIViewController
#property (weak, nonatomic) IBOutlet UISlider *valueFromSlider; //bound to your UISlider
#property (weak) IBOutlet YourGraphUIView *yourGraphUIView; //bound to your costumUIView
#property (nonatomic, retain) NSNumber *graphValue;
- (IBAction)takeSliderValue:(id)sender; //bound to your UISlider
#end
your ViewController.m:
#import "ResultViewController.h"
#import "YourGraphUIView.h"
#interface ResultViewController ()
#end
#implementation ResultViewController
#synthesize yourGraphUIView, valueFromSlider, graphValue;
- (IBAction)takeSliderValue:(UISlider *)sender{
graphValue = [NSNumber numberWithDouble:[(double)sender.value]]; //takes value from UISlider
yourGraphUIView.graphValue = graphValue; //gives the value to the yourGraphUIView
[self.yourGraphUIView setNeedsDisplay] //<--- important to redraw UIView after changes
}
end
your YourGraphUIView.h:
#import <UIKit/UIKit.h>
#interface YourGraphUIView : UIView
#property(nonatomic, retain)NSNumber *graphValue;
- (void)drawRect:(CGRect)dirtyRect;
#end
your YourGraphUIView.m:
#import "YourGraphUIView.h"
#implementation YoutGraphUIView
#synthesize graphValue;
//... init, draw rect with using the graphValue for calculating and updating the graph
end;
I hope this helped. You should have a look how to build the GUI and how to connect the UIViews. You need to set the custom classes for the ViewController and the YourGraphUIView as well. Good luck!
Related
I am trying to add a SKScene with a simple animation to a UICollectionCell.
I've got my SKView setup in the .xib file of the collection view cell.
If I run it and scroll, it works as expected. But if I change to a different view controller (in a tab bar controller) that also uses those types of cells and start scrolling, it will get stuck.
Here's how I am adding the SKScene:
#interface MAPostCollectionViewCell ()
#property (strong, nonatomic) IBOutlet SKView *viewAnimation;
#property (strong, nonatomic) MAHeartAnimation *heartScene;
#end
#implementation MAPostCollectionViewCell
-(void)layoutSubviews{
[super layoutSubviews];
self.heartScene = [MAHeartAnimation sceneWithSize:self.viewAnimation.bounds.size];
self.heartScene.scaleMode = SKSceneScaleModeAspectFill;
[self.viewAnimation presentScene:self.heartScene];
}
#end
And I only trigger the animation if the user taps a button.
But for the scope of this question, the animation doesn't really matter, as I am not touching that button, just scrolling the UICollectionView.
It turns out you should not use SpriteKit and UIKit this way. Even though they are modular frameworks, they are not performant when using together this way. I ended up using UIKit for the animations that I wanted, by using CALayers.
I have created a custom class "CustomSCNView" that inherits from SCNView. I want to use the custom class in another view controller. So I need to create a CustomSCNView object and use it to another class to manipulate things. But how can I create a CustomSCNView object in another class.
This is not working:
CustomSCNView *customView = [[CustomSCNView alloc]init]; //in viewcontroller.m
Sorry forgot to mention I used the interface builder to drag a SCNView to the view controller and then set its class to CustomSCNView.
I'm a bit confused by your question, but I've created a sample project at https://github.com/NSGod/CustomSCNView that may do what you're looking for.
First, the storyboard has 2 CustomSCNViews laid out side by side in the ViewController's view. Like you did, I dragged 2 SCNViews from the IB palette to the view and then set the custom class to be CustomSCNView.
Second, is the CustomSCNView class which is defined as follows:
#import <Cocoa/Cocoa.h>
#import <SceneKit/SceneKit.h>
#interface CustomSCNView : SCNView
#property (nonatomic, assign) BOOL allowsRotation;
#end
You can see, it has an allowsRotation property that any other object can set.
To set a default value for allowsRotation, other than NO, you can override initWithCoder: which is what's used when you set up the views in Interface Builder like you did:
#import "CustomSCNView.h"
#implementation CustomSCNView
- (id)initWithCoder:(NSCoder *)coder {
if ((self = [super initWithCoder:coder])) {
_allowsRotation = YES;
}
return self;
}
#end
The ViewController then has 2 IBOutlets to both CustomSCNViews.
#import <Cocoa/Cocoa.h>
#import <SceneKit/SceneKit.h>
#class CustomSCNView;
#interface ViewController : NSViewController
#property (weak) IBOutlet CustomSCNView *sView1;
#property (weak) IBOutlet CustomSCNView *sView2;
#end
ViewController.m:
#import "ViewController.h"
#import "CustomSCNView.h"
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_sView1.allowsRotation = NO;
_sView2.allowsRotation = YES;
}
#end
You can see that in viewDidLoad, you can set the allowsRotation property of both views to whatever you want. When you run this application, 2 instances of CustomSCNView are created for you automatically (via initWithCoder:), when the storyboard/nib files are loaded. There's no need to create another instance of a CustomSCNView to be able to set the properties of the 2 existing instances you already have.
If you look at the documentation for SCNView it tells you:
You can create a SceneKit view by using its initWithFrame:options:
method or by adding it to a nib file or storyboard.
So you cannot use the init method unless you have implemented your [CustomSCNView init] method to call [super initWithFrame:options:].
If you need access to custom subclass properties from Interface Builder, mark those properties IBInspectable (and possibly implement IBDesignable). That's documented by Apple here, and nicely summarized on NSHipster.
In any initialization path, you must call the superclass's designated initializer. For SCNView, that appears to be initWithFrame:options: (not documented as such, but the header strongly implies it). See this document on multiple initializers and subclassing.
That said, though, subclassing SCNView is a code smell that you might be fighting the framework and working too hard.
I am working on an iOS project and wanted to include a UIView that is reused on multiple screens in the application (appearing at the bottom of different UIViews). I am using a storyboard for the UI work so far but created an .xib file to be used by the reusable view (playerView in code below).
The view gets added but the button I have added to the View is unresponsive, also my background color cannot be changed on the view. I have tried to set background color programmatically and in the .xib with no luck. Very weird symptoms and I tried to instantiate the view from #5 of this article and probably did something wrong. I dont fully understand everything in the article which makes me nervous - for instance my showSubclassedView method returns IBAction but I just call the function name in code and dont use a button (I did hook up the buttons in the view as the article described though).
Here is the code:
EventViewController.m (where I trry and add PlayerView)
#import "EventViewController.h"
#import "PlayerView.h"
#interface EventViewController ()
-(IBAction)showSubclassedView;
#end
#implementation EventViewController
-(IBAction)showSubclassedView
{
[PlayerView presentInViewController:self];
}
PlayerView.h (.h for reusable view)
#import <UIKit/UIKit.h>
#import "Utils.h"
#class PlayerView;
#protocol PlayerViewDelegate
-(void)playerViewTouchedUp:(PlayerView*) playerView;
-(void)playerViewDidDismiss:(PlayerView*) playerView;
#end
#interface PlayerView : UIView
+(void)presentInViewController:(UIViewController<PlayerViewDelegate>*) playerView;
-(IBAction)viewTouchedUp;
-(IBAction)dismiss;
#end
PlayerView.m (.m for reusable view)
#import "PlayerView.h"
#interface PlayerViewOwner : NSObject
#property(nonatomic,weak) IBOutlet PlayerView *playerView;
#end
#implementation PlayerViewOwner
#end
#interface PlayerView ()
#property (nonatomic, weak) UIViewController <PlayerViewDelegate> *delegateViewController;
#end
#implementation PlayerView
+(void)presentInViewController:(UIViewController<PlayerViewDelegate> *)viewController
{
PlayerViewOwner *owner = [PlayerViewOwner new];
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:owner options:nil];
owner.playerView.delegateViewController = viewController;
[viewController.view addSubview:owner.playerView];
}
-(IBAction)viewTouchedUp
{
//forward to delegate
NSLog(#"you clicked a button");
[self.delegateViewController playerViewTouchedUp:self];
}
-(IBAction)dismiss
{
[self removeFromSuperview];
// Forward to delegate
[self.delegateViewController playerViewDidDismiss:self];
}
#end
PlayerView.xib has a UIbutton on it that connects it to viewTouchedUp method in PLayerView.m
Is there anything I did wrong in the code above? Is this the best way to do a reusable view to display on other views?
Thank you!
Here's a workaround I found after not being able to solve this.
New implementation is from this article
I reverted my changes from my initial question and implemented this. It lacks the nice protocol separation for talking back and forth and I might need something like this later but I think now I can still implement a protocol to solve this.
At least with this solution I have usable items in my view and a background that changes!
I have a AVAudioPlayer property in my viewController and I want to draw a circle in my custom uiview if the music is playing.
in my MyViewController.h:
#property (strong, nonatomic) AVAudioPlayer *audio;
in my view.h
- (void) drawCircle
{
//DRAW Circle
}
now i want to check in my View.m if the audio is playing.
i tried to add the following in my View.h
#property (nonatomic, assign) MyViewController *ViewController_audio;
and then do if ([ViewController_audio _audio].playing) { } in
but it didn't work.
Your syntax is wrong. Get rid of the underscore:
if ([ViewController_audio audio].playing)
or
if (ViewController_audio.audio.playing)
Either syntax is valid.
P.S. "...it didn't work" is supremely unhelpful. Always explain what happens. In your case, I'm assuming that you got a compiler error. So copy and paste the full text of the compiler error, and explicitly tell the line on which it occurs.
A view should not have a reference to view controller. This is a bad design.
Instead you should have a flag in view which will be set by your view controller when its playing. The view will read this property & take ui decisions.
From design prospective i would suggest that no decisions should be left to view. It is view controller who has right to think, a view should only display.
Got away from XCode programming for a while, now I'm starting back and feel like I'm starting ALL over. I have a simple example that's driving me crazy.
I have created a sub class of UIView called Word1View and added it to my storyboard.
I declared it as a property in the main view controller and attached it as an IBOutlet to the main view. I can draw on this view using Core Graphics with no problems (rectangles, lines, etc). Here's the code in the ".h" file. The property is synthesized correctly in the ".m"
#import <UIKit/UIKit.h>
#import "Word1View.h"
#interface Board3ViewController : UIViewController
{
Word1View *word1View;
}
#property (nonatomic, strong) IBOutlet Word1View *word1View;
#end
Now, here's the problem. I have added a UILabel (I will be adding UIImageFiles later)
in Word1View.h it appears as so:
#import <UIKit/UIKit.h>
#interface Word1View : UIView
{
UILabel *testLabel;
}
#property (nonatomic, retain) UILabel *testLabel;
#end
This property is also synthesized correctly. The UILabel was added to the view in the main storyboard. I tried to make this an IBOutlet, but could not hook it up as long as it was part of the subview. I tried to access the UILabel in my main view controller as so:
[super viewDidLoad];
[self.view addSubview:word1View];
NSLog(#"Label text is: %#", word1View.testLabel.text);
I don't have any access to that label as evidenced by the output to NSLog which is:
Board3[14520:c07] Label text is: (null)
I would like to be able to access the label (change its text, properties etc) from my main View Controller.
Any help is greatly appreciated. Thank you.
You need to instantiate your UILabel and give it some text.
wordView1 = [[UILabel alloc] init];
wordView.text = #"Some Text";