I'm going to create a book shelf in one of my projects. The basic requirements are as follows:
similar to iBooks bookshelf
supports both orientations well
supports all kinds of iOS devices well (different resolutions)
supports deleting and inserting items
supports reordering of items by long press gesture
shows a hidden logo when first row is pulled down
The UICollectionView is the first option that occurred to me. It easily supports grid cells. So I googled it and found some very useful tutorials:
Bryan Hansen's UICollectionView custom layout tutorial
Mark Pospesel's How to Add a Decoration View to a UICollectionView
LXReorderableCollectionViewFlowLayout
And here is the result: (Please ignore the color inconsistency problem because of the graphics I chose are not perfect yet.)
What I did:
Created a custom layout by creating a class inherited from LXReorderableCollectionViewFlowLayout (for reordering purposes) which inherited from UICollectionFlowLayout
Added a decoration view for showing logo
Added a decoration view for showing the bookshelfs
But I ran into a few problems:
1. I can't scroll at all if the items can be shown in one screen
Then I added the following code, to make the contentsize bigger
- (CGSize)collectionViewContentSize
{
return CGSizeMake(self.collectionView.bounds.size.width, self.collectionView.bounds.size.height+100);
}
Then I can scroll now. Here I pull down the first row:
You can see the the decoration view for logo is working.
2. But I got the second set of problems when I pull up the last row:
You can see the decoration view is not added at the green box part.
3. The background of decoration view for bookshelf is getting darker and darker. (Please refer to same problem here
4. The book shelf bar sometimes moves when I reorder the items
I list some of the important code here:
- (void)prepareLayout
{
// call super so flow layout can do all the math for cells, headers, and footers
[super prepareLayout];
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
NSMutableDictionary *shelfLayoutInfo = [NSMutableDictionary dictionary];
// decoration view - emblem
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
UICollectionViewLayoutAttributes *emblemAttributes =
[UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:[EmblemView kind]
withIndexPath:indexPath];
emblemAttributes.frame = [self frameForEmblem:YES];
dictionary[[EmblemView kind]] = #{indexPath: emblemAttributes};
// Calculate where shelves go in a vertical layout
int sectionCount = [self.collectionView numberOfSections];
CGFloat y = 0;
CGFloat availableWidth = self.collectionViewContentSize.width - (self.sectionInset.left + self.sectionInset.right);
int itemsAcross = floorf((availableWidth + self.minimumInteritemSpacing) / (self.itemSize.width + self.minimumInteritemSpacing));
for (int section = 0; section < sectionCount; section++)
{
y += self.headerReferenceSize.height;
//y += self.sectionInset.top;
int itemCount = [self.collectionView numberOfItemsInSection:section];
int rows = ceilf(itemCount/(float)itemsAcross)+1; // add 2 more empty row which doesn't have any data
for (int row = 0; row < rows; row++)
{
indexPath = [NSIndexPath indexPathForItem:row inSection:section];
shelfLayoutInfo[indexPath] = [NSValue valueWithCGRect:CGRectMake(0,y, self.collectionViewContentSize.width, self.itemSize.height + DECORATION_HEIGHT)];
y += self.itemSize.height;
if (row < rows - 1)
y += self.minimumLineSpacing;
}
y += self.sectionInset.bottom;
y += self.footerReferenceSize.height;
}
dictionary[[ShelfView kind]] = shelfLayoutInfo;
self.shelfLayoutInfo = dictionary;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *attributesArrayInRect = [super layoutAttributesForElementsInRect:rect];
// cell layout info
for (BookShelfLayoutAttributes *attribs in attributesArrayInRect)
{
attribs.zIndex = 1;
CATransform3D t = CATransform3DIdentity;
t = CATransform3DTranslate(t, 0, 0, 40);
attribs.transform3D = CATransform3DRotate(t, 15 * M_PI / 180, 1, 0, 0);
}
// Add our decoration views (shelves)
NSMutableDictionary* shelfDictionary = self.shelfLayoutInfo[[ShelfView kind]];
NSMutableArray *newArray = [attributesArrayInRect mutableCopy];
[shelfDictionary enumerateKeysAndObjectsUsingBlock:^(id key, NSValue* obj, BOOL *stop) {
if (CGRectIntersectsRect([obj CGRectValue], rect))
{
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:[ShelfView kind] withIndexPath:key];
attributes.frame = [obj CGRectValue];
NSLog(#"decorationView rect = %#",NSStringFromCGRect(attributes.frame));
attributes.zIndex = 0;
//attributes.alpha = 0.5; // screenshots
[newArray addObject:attributes];
}
}];
attributesArrayInRect = [NSArray arrayWithArray:newArray];
NSMutableDictionary* emblemDictionary = self.shelfLayoutInfo[[EmblemView kind]];
NSMutableArray *newArray2 = [attributesArrayInRect mutableCopy];
[emblemDictionary enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *innerStop) {
if (CGRectIntersectsRect(rect, attributes.frame)) {
[newArray2 addObject:attributes];
}
}];
attributesArrayInRect = [NSArray arrayWithArray:newArray2];
return attributesArrayInRect;
}
I'll appreciate if you're patient enough to read this post and provide any advice or suggestions. And I'll post the complete source code if I can fix all the issues. Thank you in advance.
I would suggest to check your last row and do the [self.collectionView ScrollEnable:NO] same for first row, set background color clear of collectionView and collectionViewCell and that bookshelf image sets on background view.
Related
i do have the following Code:
private static void AddElements(Canvas canvas)
{
double canvasHeight = canvas.Height;
double canvasWidth = canvas.Width;
double y0 = canvasHeight / 2;
double x0 = canvasWidth / 2;
// Defining the new Coordinate-Point (0,0) to mid auf Canvas
TranslateTransform tt = new TranslateTransform(x0, y0);
Line line1 = new Line();
line1.X1 = -350;
line1.Y1 = 0;
line1.X2 = 350;
line1.Y2 = 0;
line1.Stroke = Brushes.Black;
line1.StrokeThickness = 2.0;
line1.RenderTransform = tt;
canvas.Children.Add(line1);
Line line2 = new Line();
line2.X1 = 0;
line2.Y1 = -350;
line2.X2 = 0;
line2.Y2 = 350;
line2.Stroke = Brushes.Black;
line2.StrokeThickness = 2.0;
line2.RenderTransform = tt;
canvas.Children.Add(line2);
Label lblN = new Label();
lblN.Width = 50;
lblN.Background = Brushes.Red;
lblN.Margin = new System.Windows.Thickness(0, -350, 0, 0);
lblN.Content = $"N";
lblN.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Center;
lblN.VerticalContentAlignment = System.Windows.VerticalAlignment.Center;
lblN.RenderTransform = tt;
lblN.Padding = new System.Windows.Thickness(0);
lblN.BorderBrush = Brushes.Black;
lblN.BorderThickness = new System.Windows.Thickness(2.0);
lblN.RenderTransform = tt;
canvas.Children.Add(lblN);
Label lblS = new Label();
lblS.Width = 50;
lblS.Background = Brushes.Red;
lblS.Margin = new System.Windows.Thickness(0, 350, 0, 0);
lblS.Content = $"S";
lblS.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Center;
lblS.VerticalContentAlignment = System.Windows.VerticalAlignment.Center;
lblS.RenderTransform = tt;
lblS.Padding = new System.Windows.Thickness(0);
lblS.BorderBrush = Brushes.Black;
lblS.BorderThickness = new System.Windows.Thickness(2.0);
lblS.RenderTransform = tt;
canvas.Children.Add(lblS);
}
this method is called on an Menu-Eventhandler and it shows an coordinate system with (0,0) in the mid of the canvas. It should show a label with "N" at the top and a label with "S" at the bottom.
But i shows the attached image
Does anyone know, why lblN looks different than lblS ?
best regards
Volkhard
=============
if i set the height of both Label-Objects to 15
lblN.Height=15
:
lblS.Height=15
i get the following:
i expected the lblN to be more upper on the y-coordinate.
What's causing it
Through a bit of testing, I can definitely say that it's the lblN.Margin = new System.Windows.Thickness(0, -350, 0, 0); that's causing the problem. Apparently, when you give a Label a negative margin like that, it will move upwards only as far is it's Height, and then it will start expanding instead of just continuing to move. So you end up with a Label that's 350 tall. We could try to figure out why that is, but really, that would be missing the point.
Admittedly, I don't have any direct documentation to back up the following statement this, but from years of experience in WPF I feel I can say:
Margin is intended to be used to give space between elements in a dynamic layout, not to give an element an absolute position.
This behavior of the Label seems to strengthen the idea that using Margin in this way was not something that was planed for by the designers.
What you should do instead
Canvas has tools for giving an element a set position, yet nowhere do you use Canvas's SetLeft, SetTop, SetRight, or SetBottom. Take a look at the example on MSDN. You shouldn't need to use a TranslateTransform or set Margin at all. Instead, you should calculate where you want the element to be and use one of the above four listed methods to assign that position.
Extra Tip
Don't use canvas.Height and canvas.Width, use canvas.ActualHeight and canvas.ActualWidth instead. The first pair only work if you are explicitly setting the size of the Canvas (which it seems you are). But in a senario where the Canvas is dynamically sized, the first pair will be NaN. The second pair always return the actual size that the Canvas is.
This doesn't make a difference in your current use case, but it might later on. If you're doing calculations based on the actual size of an element (as opposed to the size you might want it to be), always use ActualHeight and ActualWidth.
I am writing a chemical molecule editor for Windows. As it has to be used in a Word Add-In I am restricted to using WPF for rendering structures. This is working quite well, apart from one tiny niggling point.
I use GlyphRuns for rendering atom labels and they are always displaced slightly to the right. If you look on the screenshot you can see there is a leading whitespace, especially with the H2N, and Hg atom labels. Why? The white background is what you get when you get the outline geometry of the glyph run.
The GlyphRun class is so badly documented that I cannot see which of the properties to amend to precisely locate the text where I want it. So any suggestions to try would be welcome.
UPDATE: I've been asked to provide a sample. The code is complex, but not gratuitously so, so I'm cutting it down to focus on the essentials:
public void MeasureAtCenter(Point center)
{
GlyphInfo = GlyphUtils.GetGlyphsAndInfo(Text, PixelsPerDip, out GlyphRun groupGlyphRun, center, _glyphTypeface, TypeSize);
//compensate the main offset vector for any descenders
Vector mainOffset = GlyphUtils.GetOffsetVector(groupGlyphRun, AtomShape.SymbolSize) + new Vector(0.0, -MaxBaselineOffset) + new Vector(-FirstBearing(groupGlyphRun), 0.0);
TextRun = groupGlyphRun;
TextMetrics = new AtomTextMetrics
{
BoundingBox = groupGlyphRun.GetBoundingBox(center + mainOffset),
Geocenter = center,
TotalBoundingBox = groupGlyphRun.GetBoundingBox(center + mainOffset),
OffsetVector = mainOffset
};
}
public static GlyphInfo GetGlyphs(string symbolText, GlyphTypeface glyphTypeFace, double size)
{
ushort[] glyphIndexes = new ushort[symbolText.Length];
double[] advanceWidths = new double[symbolText.Length];
double[] uprightBaselineOffsets = new double[symbolText.Length];
double totalWidth = 0;
for (int n = 0; n < symbolText.Length; n++)
{
ushort glyphIndex = glyphTypeFace.CharacterToGlyphMap[symbolText[n]];
glyphIndexes[n] = glyphIndex;
double width = glyphTypeFace.AdvanceWidths[glyphIndex] * size;
advanceWidths[n] = width;
double ubo = glyphTypeFace.DistancesFromHorizontalBaselineToBlackBoxBottom[glyphIndex] * size;
uprightBaselineOffsets[n] = ubo;
totalWidth += width;
}
return new GlyphInfo { AdvanceWidths = advanceWidths, Indexes = glyphIndexes, Width = totalWidth, UprightBaselineOffsets = uprightBaselineOffsets };
}
public static GlyphUtils.GlyphInfo GetGlyphsAndInfo(string symbolText, float pixelsPerDip, out GlyphRun hydrogenGlyphRun, Point point, GlyphTypeface glyphTypeFace, double symbolSize)
{
//measure the H atom first
var glyphInfo = GlyphUtils.GetGlyphs(symbolText, glyphTypeFace, symbolSize);
hydrogenGlyphRun = GlyphUtils.GetGlyphRun(glyphInfo, glyphTypeFace,
symbolSize, pixelsPerDip, point);
//work out exactly how much we should offset from the center to get to the bottom left
return glyphInfo;
}
public static Vector GetOffsetVector(GlyphRun glyphRun, double symbolSize)
{
Rect rect = glyphRun.ComputeInkBoundingBox();
//Vector offset = (rect.BottomLeft - rect.TopRight) / 2;
Vector offset = new Vector(-rect.Width / 2, glyphRun.GlyphTypeface.CapsHeight * symbolSize / 2);
return offset;
}
Indeed the GlyphRun class is a lot of work to use. I would suggest working with FormattedText objects instead. If there are performance issues, you can consider converting the FormattedText to Geometry once and reusing that. The MSDN docs provide a comparison of the different approaches.
Hey everyone so I have been at this for awhile now and I'm trying to figure out the best way to go about this. So I have an array of Movie Clip Objects calledouterPlanets and they are added to an array called aPlanetArray so these planets in the array are added to the stage and have spacing between them all. When the Player touches the screen the character jumps on another planet and the planets scroll down on the +y axis to keep the character positioned to the center.
I add 10 planets to the stage because for performance Issues I don't want to add a lot and lose FPS so my idea was when the array is getting low such as the planet array is <= 5 then add more planets to the top position of the last last planet. Hope I am making sense. Think of it like a stack of blocks the blocks fall down one by one and more is added to the top of them as more fall down so its never ending.
Here is how I add them to the stage:
//Instantiate Arrays
aPlanetArray = new Array();
//Numbers
xSpacing = 100;
ySpacing = 200;
startPoint = new Point((stage.stageWidth / 2), (stage.stageHeight / 2) );
addOuterPlanets();
private function addOuterPlanets():void
{
for (var i:int = 0; i < nPlanets; i++)
{
outerPlanets = new mcOuterPlanets();
outerPlanets.x = startPoint.x + (xSpacing * i);
outerPlanets.y = startPoint.y - (ySpacing * i);
stage.addChild(outerPlanets);
aPlanetArray.push(outerPlanets);
}
}
The only thing I can come up with at the moment is this:
if (aPlanetArray.length <= 5)
{
addOuterPlanets();
}
This adds a new set of planets but of course just adds them to the center of the stage and not on top of the other planets. Any idea how to accomplish this?
Current Progress:
private function collisionPlanetHandler():void
{
for (var i:int = 0; i < aPlanetArray.length; i++)
{
var currentPlanet:mcOuterPlanets = aPlanetArray[i];
planetContainer.addChild(aPlanetArray[i]);
if (character.hitTestObject(currentPlanet) && !nextlevel)
{
trace("HIT");
yDown = (stage.stageHeight / 2) - (currentPlanet.y - 200); //have object tween to center of stage or where was last positioned
//tap back to false
tap = false;
nextlevel = true;
if (!bNullObject) // have null object so doesnt loop again and cause error for planet == null
{
planet.destroy();
planet = null;
}
bNullObject = true;
planetHit = currentPlanet; // to land on correct planet
aPlanetArray.splice(i, 1);
randomRotation = randomNumber(1, 2); //Stop in random rotation for next planet
TweenLite.to(planetContainer, 2.0, { y:yDown, ease:Elastic.easeOut } );
planetIncrement -= 300;
addPlanet(randomNumber((stage.stageWidth/2) - 220, ((stage.stageWidth/2)) + 220), planetIncrement);
}
}
}
example code
function gameSetup():void{
setupUsers();
loadSounds();
createLevel(_level1);
addInitialPlanets();
addCharacter();
}
private function addInitialPlanets():void{
for (var i:int = 0; i < nPlanets; i++){
addPlanet(startPoint.x + xSpacing * i, startPoint.y + ySpacing * i);
}
}
private function addPlanet(xPos:Number, yPos:Number):void{
p = new mcOuterPlanets();
// var p:mcOuterPlanets = new mcOuterPlanets(); // this is preferred method
p.x = xPos;
p.y = yPos;
stage.addChild(p);
// addChild(p); // this is preferred method
aPlanetArray.push(p);
}
Now when you need to add another planet above the others do
addPlanet(xPos, yPos);
// where xPos is desired X and yPos is desired y
You see what is happening here? I'm positioning the planet by using a separate function. You can offload other tasks related to add a planet as well. Imagine something like this:
addPlanet(xPos, yPos, color, size, speed, ringCount);
Get the idea?
Also you'll want to remove the planets that are far below the player to prevent slowdown. Or you could simply move the ones far below up to the top to recycle them instead of creating new planets all the time.
Draft example...
#Nathan , Neal Davis is right as usual, but I'd push the Vector, then addChild at its current index... Avoid the Arrays if You can. (if the instances are made by the same Class).
var aPlanetArray:<Vector>.MovieClip = new <Vector>.MovieClip(10);
// or new <Vector>.MovieClip(); if You don't want to restrict the amount of items in the Vector.
// or new <Vector>.McOuterPlanets() if McOuterPlanets is a Class.
private function addOuterPlanets():void {
for (var i:int = 0; i < nPlanets; i++){
aPlanetArray.push(new mcOuterPlanets());
aPlanetArray[i].x = startPoint.x + (xSpacing * i);
aPlanetArray[i].y = startPoint.y - (ySpacing * i);
addChild(aPlanetArray[i]);
};
[EDIT]
If you choose to make a Vector of MovieClip (var aPlanetArray:<Vector>.MovieClip = new <Vector>.MovieClip(10)),
You must use aPlanetArray[i] as McOuterPlanets to get the methods of
Your McOuterPlanets methods!
So, in the case of a Vector of MovieClip (aPlanetArray == .MovieClip), if You want to use the methods of McOuterPlanets methods You have to do :
var outerP : McOuterPlanets = aPlanetArray[someIndex] as McOuterPlanets;.
Then You may call outerP.someMethodOfMcOuterPlanets();
Just because McOuterPlanets extends the MovieClip Class.
Never use Vectors if You have to add different types of datas in Your Vector, use the Array Class instead.
This will works but this is tricky an total nonsense!
Example :
var vectorOfStrings:Vector.<String> = Vector.<String>([["a","b","c"],["d","e","f"]]);
// this works and Your Vector contains only Strings so OK.
trace("");
trace("length of the Vector = " + vectorOfStrings.length);
trace("vectorOfStrings.toString() = " + vectorOfStrings.toString());
trace("vectorOfStrings[0] = " + vectorOfStrings[0]);
trace("vectorOfStrings[1] = " + vectorOfStrings[1]);
var vectorOfArrays:Vector.<Array> = Vector.<Array>([["a","b","c"],[1,2,3]]);
// this works but this is tricky an total nonsense
// use the Array Class instead!
trace("\n NEVER DO THIS! Use the Array Class instead!");
trace(" NONSENSE!");
trace("length of the Vector = " + vectorOfArrays.length);
trace("vectorOfArrays.toString() = " + vectorOfArrays.toString());
trace("vectorOfArrays[0] = " + vectorOfArrays[0]);
trace("vectorOfArrays[1] = " + vectorOfArrays[1]);
[/EDIT]
I hope this may help.
Check to the reference for :
ActionScript 3 fundamentals: Arrays
ActionScript 3 fundamentals: Associative arrays, maps, and dictionaries
ActionScript 3 fundamentals: Vectors and ByteArrays
I don't know if it is even possible, but maybe someone found a way to do this...
I have a tab control to which I allow the user to add tabs with a button click.
I want to show some icons on the tab so I added an ImageList, but I can show only one icon at a time, and I need to show at least 3 icons together.
I thought about having an image of 3 icons together, but the icons are shown after some actions the use do. For example: at first I show icon_1 and if the user clicks some where I add icon_2 etc...
Can someone come up with a way to do this ?
Thank you very much in advance...
No. It's not possible. Using the standard WinForms TabControl component you only can show one image at the same time.
The solution here, is using overlay icons. You have a base icon, and you add decorators. This is how Tortoise SVN, for example,
The following code builds an overlayed image in C#:
private static object mOverlayLock = new object();
public static Image GetOverlayedImage(Image baseImage, Image overlay)
{
Image im = null;
lock (mOverlayLock)
{
try
{
im = baseImage.Clone() as Image;
Graphics g = Graphics.FromImage(im);
g.DrawImage(overlay, 0, 0, im.Width, im.Height);
g.Dispose();
}
catch
{
// log your exception here
}
}
return im;
}
NOTE: The overlayed image must have the same size than the base image. It must have transparent color, and the decorator in the overlayed image must be placed in the right place, for example bottom-right or top-right.
I found this code:
private Bitmap CombineImages(params Image[] images)
{
int width = 0;
for (int i = 0; i < images.Length; i++)
width += images[i].Width + 3;
int height = 0;
for (int i = 0; i < images.Length; i++)
{
if (images[i].Height > height)
height = images[i].Height;
}
int nIndex = 0;
Bitmap fullImage = new Bitmap(width, height);
Graphics g = Graphics.FromImage(fullImage);
g.Clear(SystemColors.AppWorkspace);
foreach (Image img in images)
{
if (nIndex == 0)
{
g.DrawImage(img, new Point(0, 0));
nIndex++;
width = img.Width;
}
else
{
g.DrawImage(img, new Point(width, 0));
width += img.Width;
}
}
return fullImage;
//img3.Save(finalImage, System.Drawing.Imaging.ImageFormat.Jpeg);
//img3.Dispose();
//imageLocation.Image = Image.FromFile(finalImage);
}
from this link http://www.codeproject.com/Articles/502249/Combineplusseveralplusimagesplustoplusformplusaplu
I import a model using the Helix3d toolkit. Next, I perform a hit-test on the 3Dmodel. ThenI get the MeshGeometry3D information from rayMeshResult.Meshit. Finally I get the vertex position information, and triangleIndices from the MeshGeometry3D. Now I clone the points from LinesVisual3D, and then I feed the triangleIndices/vertex info into the clone. Finally, I copy the clone's point data into LinesVisual3D.Points, and add the lines to my viewport.
As you can see from the picture at the link, not all of the edges of the cube are drawn, yet all of the points are there.
http://www.freeimagehosting.net/fhws5
GeometryModel3D hitgeo = rayMeshResult.ModelHit as GeometryModel3D;
MeshGeometry3D newGeom = rayMeshResult.MeshHit as MeshGeometry3D;
Point3DCollection srtpnt = modelLines.Points.Clone();
for (int i = 0; i < newGeom.TriangleIndices.Count; i ++)
{
srtpnt.Add(newGeom.Positions[newGeom.TriangleIndices[i]]);
textBlock4.Text += newGeom.Positions[newGeom.TriangleIndices[i]].ToString() + "\n";
}
modelLines.Points = srtpnt;
modelPoints.Points = srtpnt;
modelPoints.Color = Colors.Red;
modelPoints.Size = 15;
modelLines.Thickness = 6;
modelLines.Color = Colors.Blue;
MainViewport.ClearChildren();
MainViewport.Children.Add(modelLines);
MainViewport.Children.Add(modelPoints);
UpdateResultInfo(rayMeshResult);