Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
pokeyman
Nov 26, 2006

That elephant ate my entire platoon.

Kallikrates posted:

You can directly access a textviews textStorage, (usually an attributed string), but read the docs, depending on the way you do this, you can break the built in undomanager if you even need it.

docs posted:

NSTextStorage Class Reference

Inherits from NSMutableAttributedString : NSAttributedString : NSObject

Adbot
ADBOT LOVES YOU

Kallikrates
Jul 7, 2002
Pro Lurker
It's possible to subclass it such that it's not an attributed string.

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
Who cares? Someone using an instance of NSTextStorage is free to use it as if it was a mutable attributed string, and they should if it's useful to them.

I didn't want someone to see "usually" and think "oh gee, maybe I can't just edit the text storage directly". You can.

Kallikrates
Jul 7, 2002
Pro Lurker

Kallikrates posted:

You can directly access a textviews textStorage,

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice

pokeyman posted:

Stuff

I know, Chinese and Turkish are the immediate examples. We can just agree to disagree, I wouldn't be the first programmer to have a stubborn belief.

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
I spent a good hour thinking about it because I really really wanted to agree with you. So I hope you figure out a way to make it all work!

Books On Tape
Dec 26, 2003

Future of the franchise
What are the current options for iOS development on Windows? I'm new to this, and have ordered the big nerd ranch books. I want to do the exercises in the books but dont really want to shell out cash for a mac or a developer account until I know I want to do this for real. So long as I can complie and test the examples, I'll be ok for now.

Froist
Jun 6, 2004

jerkstore77 posted:

What are the current options for iOS development on Windows? I'm new to this, and have ordered the big nerd ranch books. I want to do the exercises in the books but dont really want to shell out cash for a mac or a developer account until I know I want to do this for real. So long as I can complie and test the examples, I'll be ok for now.

You can't. You might be able to run OSX in a VM, but that breaks Apple's EULA.

some kinda jackal
Feb 25, 2003

 
 

jerkstore77 posted:

What are the current options for iOS development on Windows? I'm new to this, and have ordered the big nerd ranch books. I want to do the exercises in the books but dont really want to shell out cash for a mac or a developer account until I know I want to do this for real. So long as I can complie and test the examples, I'll be ok for now.

If you've got some decently compatible hardware you can just try to hackintosh your PC, but that's out of the scope of this thread.

Hog Obituary
Jun 11, 2006
start the day right
What's the current best practice for storing user data to disk on iOS? Is it NSUserDefaults? This is not exactly "preference" data that I'm talking about (which seems like an obvious case for NSUserDefaults). This is more, app-generated data based on user action... kind of like game state, IDE workspace state, etc.

Currently I have it all in NSCoding-conforming objects that can be serialized to a file on disk, but sometimes this serializing can take longer than 10s (depending on what else the OS is doing I guess), so syncing when the app is entering background can cause the app to get killed by the system.

I can start making a separate thread that periodically syncs the data, but I'd like to avoid the added complexity if I can. (or if I should really be using NSUserDefaults for this).

One drawback of NSUserDefaults is that it won't just accept any old NSCoding-conforming objects... they have to be dictionaries, arrays, or NSDatas (which I can massage my objects into).

I'm a bit unclear on the size limits or performance concerns with NSUserDefaults.

OHIO
Aug 15, 2005

touchin' algebra

Hog Obituary posted:

What's the current best practice for storing user data to disk on iOS? Is it NSUserDefaults? This is not exactly "preference" data that I'm talking about (which seems like an obvious case for NSUserDefaults). This is more, app-generated data based on user action... kind of like game state, IDE workspace state, etc.

Currently I have it all in NSCoding-conforming objects that can be serialized to a file on disk, but sometimes this serializing can take longer than 10s (depending on what else the OS is doing I guess), so syncing when the app is entering background can cause the app to get killed by the system.

I can start making a separate thread that periodically syncs the data, but I'd like to avoid the added complexity if I can. (or if I should really be using NSUserDefaults for this).

One drawback of NSUserDefaults is that it won't just accept any old NSCoding-conforming objects... they have to be dictionaries, arrays, or NSDatas (which I can massage my objects into).

I'm a bit unclear on the size limits or performance concerns with NSUserDefaults.

Is it "document-like"? You could check out UIDocument. Otherwise it might be worth doing a quick overview of CoreData to see if it's applicable to your situation.

Hog Obituary
Jun 11, 2006
start the day right

OHIO posted:

Is it "document-like"? You could check out UIDocument. Otherwise it might be worth doing a quick overview of CoreData to see if it's applicable to your situation.

Oops, yeah I meant to end my post asking if CoreData was the "right" thing here. It's not document-like. It's mostly a bunch of dictionaries and derived objects with config-like data that comes from a server and is then used to tell the UI how to display stuff.

So I'm correct in thinking that NSUserDefaults isn't the correct thing then? Because my understanding is that it *does* do the automatic sync-ing to disk and stuff.

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice
It's not that hard to do it in a thread:

code:
dispatch_async(queue, ^{
   //do crap
   dispatch_async(dispatch_get_main_queue(), ^{
      //do UI stuff
   });
});
If everything else already works then you should be able to make that stuff async relatively easily.

Hog Obituary
Jun 11, 2006
start the day right
Fair enough, but then I have to either synchronize it or copy it... and that requires care, causes bugs, can slow down the UI, and is basically work. :P

I'll have to take some time to design it and think about what intervals i want to save different bits of data (which may be a good idea anyway)... perhaps mark them dirty, etc.

I'll look at CoreData first, thanks!

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice
Ah ok, if you need to do more complex sync then CoreData may help. Stuff like NSFetchedResults controller can give you automatic updates of a table when a background thread saves changes to a MOC.

I just thought you wanted to checkpoint the data once in a while, in which case handing a copy off to a dispatch queue would work and making the copy should be quite fast.

If you are doing stuff this complex then NSUserDefaults would be laughably inappropriate.

Kallikrates
Jul 7, 2002
Pro Lurker
If you are pulling stuff from a server into coredata take a look at restkit. Depending on your use it might be a good fit.

Hog Obituary
Jun 11, 2006
start the day right
Sweet, thanks for the advice!

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice
Quick addition to my previous pro tip #958825:

If you are debugging on a device you are running on ARM and "po $eax" will not print the exception on a debug breakpoint because the eax register does not exist.... instead you want register r4: "po $r4".

For some reason on devices the all exceptions breakpoint seems to break before the exception object is constructed, so you may have to step forward in the assembly view 5-10 steps before r4 will actually hold the address of the exception.

Fat Whale
Feb 20, 2012

O O O O O O O O O O
Build iOS apps with Ruby (from the creator of MacRuby):

http://www.rubymotion.com/

Pretty cool.

Fat Whale fucked around with this message at 22:25 on May 3, 2012

xgalaxy
Jan 27, 2004
i write code
I've got a 'best practice' question regarding IBOutlets.

Say in InterfaceBuilder I've got a UIView with a UIScrollView inside of it.
My only interaction with this UIScrollView in the view controller is to setup the contentSize property. This is done in viewDidLoad of the view controller.

There seems to be a couple of different ways of getting access to the UIScrollView IBOutlet.

1) I could create a public property that retains the IBOutlet UIScrollView
2) I could create a category in the .m file with a property that retains the IBOutlet UIScrollView
3) I could create a @private ivar IBOutlet UIScrollView

Now, coming from a C++ background I have an overwhelming desire to not create a publicly accessible property or ivar. Which leaves me with options 2 and 3. And to be honest, I find option #3 to be the most elegant.

I guess I have a couple of questions though.

First, if I go with option three I'm confused on if the UIScrollView ends up getting retained (automatically) or not? I'm thinking it doesn't, which leads me to my next question.

Does it matter if it gets retained? Shouldn't the UIView parent be holding a retain on it anyway?

Lastly, what does everyone else do?

xgalaxy fucked around with this message at 05:32 on May 4, 2012

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
On iOS, outlets get set by the nib loader by sending -setValue:withKey:. So if you have a property, its accessor gets called, and if you have an ivar it simply gets set, all according to the usual key-value coding machinery.

Thus the answer to "does it get retained" depends on whether the ivar or property is defined as strong/retain. As outlets will not get retained for you you'd best retain them yourself, so declare the property strong and clear them out in -viewDidUnload. And you can have outlets to objects in a nib that are not in the view hierarchy, so relying on views retaining subviews won't bail you out.

As for visibility, I follow the same general rule as for all properties: if nobody else has any business mucking with it then I keep it private, i.e. in a class extension in the .m file. Which describes every IBOutlet I've come across in recent memory. Ivar versus property is a stylistic choice we've discussed in the last few pages, feel free to refer to those.

pokeyman fucked around with this message at 06:46 on May 4, 2012

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice
A view retains all it's sub views, so I highly recommend making your IBOutlets weak references. That allows UIKit to automatically dump them under memory pressure since you aren't holding unnecessary references to them. The alternative is to nil them out in viewDidUnload but why manage it manually? Thats one benefit of using XIBs - the framework will handle recreating the view if it comes on screen again, including hooking up all the outlets and actions again.

There is no real issue having the properties declared in the header/public. I know it can seem odd from a pure design standpoint but in practice it just doesn't matter. But as pokeyman pointed out you can just declare the properties in your implementation file to make them private. In fact you can do fancy things like declare them readonly in the header and redefine them as readwrite in the implementation.

Toady
Jan 12, 2009

xgalaxy posted:

I've got a 'best practice' question regarding IBOutlets.

Say in InterfaceBuilder I've got a UIView with a UIScrollView inside of it.
My only interaction with this UIScrollView in the view controller is to setup the contentSize property. This is done in viewDidLoad of the view controller.

There seems to be a couple of different ways of getting access to the UIScrollView IBOutlet.

1) I could create a public property that retains the IBOutlet UIScrollView
2) I could create a category in the .m file with a property that retains the IBOutlet UIScrollView
3) I could create a @private ivar IBOutlet UIScrollView

Now, coming from a C++ background I have an overwhelming desire to not create a publicly accessible property or ivar. Which leaves me with options 2 and 3. And to be honest, I find option #3 to be the most elegant.

I guess I have a couple of questions though.

First, if I go with option three I'm confused on if the UIScrollView ends up getting retained (automatically) or not? I'm thinking it doesn't, which leads me to my next question.

Does it matter if it gets retained? Shouldn't the UIView parent be holding a retain on it anyway?

Lastly, what does everyone else do?

Apple's docs have a section on this. Declare a weak property for the IBOutlet (you should use ARC if you're not already). You typically create strong references to top-level objects in the nib and weak references to anything else, as non-top-level objects are already retained by their owners in the view hierarchy of the nib. I declare IBOutlets in a private class extension unless they need to be accessed publicly.

duck monster
Dec 15, 2004

Fat Whale posted:

Build iOS apps with Ruby (from the creator of MacRuby):

http://www.rubymotion.com/

Pretty cool.

Not for $200 it aint.

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.

duck monster posted:

Not for $200 it aint.

Yeah wow, plus more for updates past the first year. I'm not sure that's worth abandoning macruby for a year.

Carthag Tuek
Oct 15, 2005

Tider skal komme,
tider skal henrulle,
slægt skal følge slægters gang



xgalaxy posted:

I've got a 'best practice' question regarding IBOutlets.

Say in InterfaceBuilder I've got a UIView with a UIScrollView inside of it.
My only interaction with this UIScrollView in the view controller is to setup the contentSize property. This is done in viewDidLoad of the view controller.

There seems to be a couple of different ways of getting access to the UIScrollView IBOutlet.

1) I could create a public property that retains the IBOutlet UIScrollView
2) I could create a category in the .m file with a property that retains the IBOutlet UIScrollView
3) I could create a @private ivar IBOutlet UIScrollView

Now, coming from a C++ background I have an overwhelming desire to not create a publicly accessible property or ivar. Which leaves me with options 2 and 3. And to be honest, I find option #3 to be the most elegant.

I guess I have a couple of questions though.

First, if I go with option three I'm confused on if the UIScrollView ends up getting retained (automatically) or not? I'm thinking it doesn't, which leads me to my next question.

Does it matter if it gets retained? Shouldn't the UIView parent be holding a retain on it anyway?

Lastly, what does everyone else do?

Do you already have access to whatever view is inside the scroll view? Dunno about Cocoa Touch but in Cocoa a view that's inside a scroll view has a readonly property/method call where you can get at it:

quote:

enclosingScrollView
Returns the nearest ancestor NSScrollView object containing the receiver (not including the receiver itself); otherwise returns nil.

- (NSScrollView *)enclosingScrollView

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
And that'd be a trivial method to add to UIView in a category.

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice
Speaking of categories, let me re-post one of my previous ProTips that a lot of new people aren't aware of.

I've seen a ton of people say Categories can't add new properties to an object but that's not true!

Meet our friends objc_setAssociatedObject and objc_getAssociatedObject. They allow you to arbitrarily attatch an object to another object.

So feel free to declare a new property in a category. Then instead of @synthesize, just create your getter and setter and make the appropriate calls to get/set associated object.

For ARC folks, OBJC_ASSOCIATION_RETAIN is equivalent to strong and OBJC_ASSOCIATION_ASSIGN matches up with weak. The behavior is identical, including zeroing weak references. Plus if the property has the last reference to an object and the object is destroyed then the associated object will be destroyed too (no need to manually clear the associations in dealloc).

Personally I somewhat regularly find myself wanting to attatch arbitrary objects to other objects, especially UI elements like pickers and whatnot, so I added a category on NSObject that adds associatedObjectByKey: and setAssociatedObject:forKey:. Then I can fire and forget the picker and retrieve what I was supposed to do with that picker/pop up in the delegate handler, without having to create an ivar to hold the reference. Think of it like tag, only hella more useful since you can store multiple values and/or objects.

Carthag Tuek
Oct 15, 2005

Tider skal komme,
tider skal henrulle,
slægt skal følge slægters gang



The Obj-C language mailing list has an interesting discussion on a proposed feature, Range literals:

quote:

Subject: range literals
From: Jonathan Schleifer <email@hidden>
Date: Sat, 05 May 2012 20:58:15 +0200
Authentication-results: symauth.service.identifier

Now that we're introducing new literals all over the place, the idea came to my mind to add a literal for ranges. The syntax I'm thinking of is 0..3 for example. This would, however, be indices, so A..B is actually NSMakeRange(A, B - A).

When we have that, we could implement substrings in the form of:

someString[0..3]

We could do the same for arrays etc.

The .. syntax should now create any problems, at least I can't think of a case where two dots are allowed to follow each other without anything between them.

Any thoughts? I thought this would make working with strings / arrays much nicer :).

--
Jonathan

http://lists.apple.com/archives/objc-language/2012/May/msg00000.html

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
As a big fan of the literals for arrays, dictionaries, and boxed numbers...

I really dislike those range literals.

I'm not even sure why.

Doc Block
Apr 15, 2003
Fun Shoe
One of the nice things about Objective-C is that it doesn't try to have every conceivable feature, at least not as part of the language.

I'm looking forward to literals and the indexing stuff. This, not so much.

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice
Another pro-tip #86729: When working with blocks are you often annoyed that the return type goes on different sides of the ^ operator depending on the situation? And that typedefs are also subtly different too?


Typedefs are wordy in the middle, Declaration are anonymous leftists, but Implementations are right-as-rain.

Start with typedefs... they "name" the block, but in the middle. How odd!
code:
typedef NSString*(^MyNameIsFancytownBlock)(id param1, id param2);
Declarations are just the same, but prefer to remain anonymous.
code:
-(void)methodTakingBlock:(NSString* (^) (id param1, id param2))block;
While Implementation puts the return type on the right and dispenses with those socialist group-hugging parenthesis. What, you think we're all in this together?
code:
x = ^NSString*(id param1, id param2)
* If someone can come up with a better easy-to-remember saying please do so, this was my best effort :hurr:

Simulated fucked around with this message at 00:49 on May 6, 2012

Carthag Tuek
Oct 15, 2005

Tider skal komme,
tider skal henrulle,
slægt skal følge slægters gang



pokeyman posted:

As a big fan of the literals for arrays, dictionaries, and boxed numbers...

I really dislike those range literals.

I'm not even sure why.

Cause its a mess. I figured I'd put it here clean regardless tho.

xgalaxy
Jan 27, 2004
i write code
That's why I like C++11's using alternative to typedef
code:
// old syntax
typedef void (*SomeFunction)(double);

// new syntax
using OtherFunction = void (*)(double);
Could be in Objective-C:
code:
// old syntax
typedef NSString*(^SomeBlock)(id param1, id param2);

// new syntax
using OtherBlock = NSString* (^)(id param1, id param2);
Someone make it happen =D

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
It's probably easier to think of block expressions as having weird syntax, while the other two cases follow the same basic rule.

What you're calling the typedef syntax is just the normal C declaration grammar, called a declarator. For example, it's how you'd declare a local variable, ivar, property, etc. of block type.

The syntax for an Objective-C method parameter puts the name off by itself (for historical reasons, really, but it also looks nicer), so the type follows a grammar that's exactly like a declarator, but without a name. It's called an abstract-declarator, and it's the same grammatical production that you'd put in a cast.

In a block expression, you're also writing an abstract-declarator, except (1) you're writing a function type, not a pointer-to-function type, so you don't need a second caret or the weird parentheses, and (2) you can leave bits of it out, including either or both of the return type (which gets inferred according to the return statements) and the parameters (which get inferred to be empty).

Carthag Tuek
Oct 15, 2005

Tider skal komme,
tider skal henrulle,
slægt skal følge slægters gang



void (^whatever)(...);

duck monster
Dec 15, 2004

Doc Block posted:

One of the nice things about Objective-C is that it doesn't try to have every conceivable feature, at least not as part of the language.

I'm looking forward to literals and the indexing stuff. This, not so much.

Yeah. Its one thing I like about python. Because its so conservative about adding new major language features, its very easy to read fairly advanced python code and *completely* understand it.

ObjC is like that too, and it makes it very readable once you've wrapped your head around the whacky square bracket syntax.

Sometimes languages like C++ I honestly feel lost figuring out what the gently caress is going on sometimes.

That said, I still haven't fully grokked the blocks stuff yet. Mainly for lack of a good tutorial that tells me how to write objects that consume them, and the general implications for scope, thread safety, etc.

Cerebral Mayhem
Jul 18, 2000

Very useful on the planet Delphon, where they communicate with their eyebrows
IOS Coding newbie here. I'm coming from a background mainly in C.

I want to display a large amount of scrolling, changing text in real time. By way of example in C:

code:
#include <stdio.h>

int main()
{

    int i;
    for (i=1; i<=1000; i++)
    {
        printf("Stupid poo poo...%i\n", i);
        
    }

    printf ("\nYou printed %i lines of stupid poo poo!\n", i-1);
    return 0;
}
The following code will produce the same result in IOS, but I don't get to see it happen as it happens. I created a UITextView and write to it. The screen freezes during execution and boom! All the output appears at once at the end.
code:
- (IBAction)DoShitHere:(id)sender
{
    int i;
    for (i=1; i<=1000; i++)
    {
        ShitWindow.text = [ShitWindow.text stringByAppendingFormat:@"Stupid poo poo...%i\n", i]; 
    }
    
    ShitWindow.text = [ShitWindow.text stringByAppendingFormat:@"\nYou printed %i lines of stupid poo poo!\n", i-1];
}
I tried using [ShitWindow setNeedsDisplay]; but it won't force a refresh. I want to see the lines of text print as they print.

xgalaxy
Jan 27, 2004
i write code

Cerebral Mayhem posted:

I tried using [ShitWindow setNeedsDisplay]; but it won't force a refresh. I want to see the lines of text print as they print.

The reason the C example doesn't stall is because the output is going to stdout which can display the contents independent of your programs process. In the Obj-C example you are effectively stalling the run loop by setting the text property over and over without giving the system time to draw it.

Try an NSTimer, or performSelector: withObject: afterDelay:, or use GCD (grand central dispatch) to perform the append after the next run loop.

xgalaxy fucked around with this message at 18:15 on May 6, 2012

Adbot
ADBOT LOVES YOU

Simulated
Sep 28, 2001
Lowtax giveth, and Lowtax taketh away.
College Slice

xgalaxy posted:

That's why I like C++11's using alternative to typedef
code:
// old syntax
typedef void (*SomeFunction)(double);

// new syntax
using OtherFunction = void (*)(double);
Could be in Objective-C:
code:
// old syntax
typedef NSString*(^SomeBlock)(id param1, id param2);

// new syntax
using OtherBlock = NSString* (^)(id param1, id param2);
Someone make it happen =D

I don't know for certain if C# is the first C-style language to use this but it's had that since the beginning and I it's a feature I've grown to love... of course when you have namespaces it is absolutely necessary to resolve conflicts without having to type the FQN constantly.

Of course not everything in C++11 is intelligent. Their lambda syntax sucks balls. I understand wanting to be able to declare whether the captured variables are by-ref or by-val but the [] should be optional and in that case capture any referenced vars and have the compiler default to by-val const. That would (IMHO) cover the 99% case without requiring you to stop and think about it.

I think the way Apple designed Objective-C blocks is beautiful and makes way more sense - the rules are somewhat along those lines. Regular vars are copied by-val, except if qualified with __block in which case they are still stack-allocated unless you call Block_copy, then they get promoted to heap variables (Block_copy is generally required for a block to be useful outside its containing scope).

In both cases I would guess a lot of the time you are operating on heap objects so by-val just copies the pointer to the heap object and all ivar/property accesses end up where you expect anyway.


On the other hand: about loving time with enums and that is something I'd like to see in C/Objective-C post-haste. Dumping all the enum members into the global namespace is the dumbest idea in the history of computer science, requiring everyone to essentially repeat the name of the enum in every member (except where the enum name is at the end for some enums in which case the Intellisense/hints are less than useless in showing you your options).

I still think something like C# attributes would be useful for doing stuff like indicating that an enum represents combinable flags vs single values, plus a lot of what is coded through interfaces and convention today could be driven by introspection of attributes on the types and it would let you code-first, generate later (e.g.: Create CoreData classes entirely in code then use introspection to look for stuff like NSEntityAttribute, NSEntityPropertyAttribute, etc tagging the classes/properties/etc to generate the CoreData model)



rjmccall posted:

It's probably easier to think of block expressions as having weird syntax, while the other two cases follow the same basic rule.

What you're calling the typedef syntax is just the normal C declaration grammar, called a declarator. For example, it's how you'd declare a local variable, ivar, property, etc. of block type.

The syntax for an Objective-C method parameter puts the name off by itself (for historical reasons, really, but it also looks nicer), so the type follows a grammar that's exactly like a declarator, but without a name. It's called an abstract-declarator, and it's the same grammatical production that you'd put in a cast.

In a block expression, you're also writing an abstract-declarator, except (1) you're writing a function type, not a pointer-to-function type, so you don't need a second caret or the weird parentheses, and (2) you can leave bits of it out, including either or both of the return type (which gets inferred according to the return statements) and the parameters (which get inferred to be empty).

Seriously dude, you enlighten me with almost every post you make :science: Please don't ever stop.

I guess I typically think of the method syntax as "normal" because that's the one I see most often, but this totally makes sense.

Duck monster: Check out the WWDC videos if they are still up... IIRC It's the WWDC 2010 videos that really dig into blocks but they are an awesome introduction.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply