StoryBoard学习(5):使用segue页面间传递数据
函数:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
具体例子:
1.首先创建1个Single View模版项目,然后在MainStoryboard中添加1个新的ViewContronller。并在2个View Controller中添加标签、按钮、编辑输入框。
2. 将第1页和第2页建立segue。
选中第1页中的按钮[跳到第2页],鼠标右键(或按住Controll键,鼠标左键)拖拽到第2页后,放手,在弹出菜单上选择[modal]
3. 将第1页中的编辑输入框组件与class文件挂钩。
单独窗口打开MainStoryboard和ViewController.h文件,然后鼠标右键拖拽编辑输入框到.h文件中的@interface的下一行。
注意,编辑输入框组件的name属性这里设置为 page1Data
4.在ViewController.m中添加如下代码。
在@implementation之后添加:
@synthesize page1Data;
重载prepareForSegue方法:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ NSString* data = page1Data.text; UIViewController* view = segue.destinationViewController; if ([view respondsToSelector:@selector(setParam:)]) { [view setValue:data forKey:@"param"]; } }
注意:其中的setParam和param会和下面的代码进行关联!!
5.添加新类文件并绑定给第2页。
注意:Class名为 SecondViewController
将类SecondViewController绑定到第2页.
6.将第2页中的编辑输入框组件绑定到类SecondViewController中。
单独窗口打开SecondViewController.h和MainStoryboard,然后选中第2页中的编辑输入框,然后鼠标右键拖拽编辑输入框到SecondViewController.h文件中的@interface的下一行。
注意:绑定时编辑输入框组件的name属性设置为 page2Data。
7.修改SecondViewController的.h和.m文件g">
8. 给第2页中的按钮[关闭窗口]添加事件。
单独窗口打开SecondViewController.h和MainStoryboard,然后选中第2页中的按钮[关闭窗口],右键拖拽到SecondViewController.h中@interface SecondViewController : UIViewController之后;
在弹出窗口的Connection属性选择Action,Name属性设置为closeWin。然后点按钮[Connect]。
可以看到,在SecondViewController.h中增加了如下代码:
在SecondViewController.m中增加了如下代码:
修改 SecondViewController.m中的closeWin方法,具体代码如下:
- (IBAction)closeWin:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; }
编译运行,当显示第2页后,点击[关闭窗口]按钮,即可关闭第2页,重新显示第1页。
接下来,要实现在第2页修改接收到的参数,然后将修改后的参数返回给第1页。
9. 修改 ViewController.h和.m文件内容。
9.1 ViewController.h
在 @interface ViewController : UIViewController 之后添加:
@property (strong,nonatomic) NSString* editData;
注意:定义的变量 editData 是用来接收 第2页编辑输入框的内容,在 SecondViewController.m会用到!!!
9.2 ViewController.m
在 @implementation ViewController 之后添加:
@synthesize editData;
修改方法 -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ NSString* data = page1Data.text; UIViewController* view = segue.destinationViewController; if ([view respondsToSelector:@selector(setParam:)]) { [view setValue:data forKey:@"param"]; } if ([view respondsToSelector:@selector(setFirstViewController:)]) { [view setValue:self forKey:@"firstViewController"]; } }
注意:其中的 setFirstViewController 和 firstViewController 在下面的 SecondViewController.h和.m中会定义!!!
重载方法 -(void)viewWillAppear:(BOOL)animated :
-(void)viewWillAppear:(BOOL)animated{ NSLog(@"viewWillAppear"); [super viewWillAppear:animated]; page1Data.text=editData; }
10. 修改 SecondViewController.h和.m文件内容。
10.1 SecondViewController.h
在 @interface SecondViewController : UIViewController 之后添加:
@property (strong,nonatomic) id firstViewController;
注意:变量名称 firstViewController必须和上面ViewController.m中相同!!!
10.2 SecondViewController.m
在 @implementation SecondViewController 之后添加:
@synthesize firstViewController;
重载方法 -(void)viewWillDisappear:(BOOL)animated
-(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; if ([firstViewController respondsToSelector:@selector(setEditData:)]) { [page2Data endEditing:YES]; [firstViewController setValue:page2Data.text forKey:@"editData"]; } }
注意:其中的 setEditData 和 editData 必须和上面 ViewController.h和.m中定义的相同!!!
OK,编译运行。第1页的输入文字会传递到第2页的输入框中,第2页的输入框文字在关闭窗口后同样会传递给第1页的输入框中。
HTML5 正成为移动领域中的重要一员,有78%的开发人员计划2012年在他们的应用中集成这项Web技术,其中选择将其混合使用的开发人员占72%,而有6%的开发人员选择使用纯HTML5开发应用程序。
Facebook vs. Google. 开发人员正努力理解和利用Facebook的社交图谱(Social Graph)。尽管Facebook拥有超过425,000,000的移动用户以及接近900,000,000的常规用户,但是仅有61%的开发人员认为 Facebook会在社交战略中发挥主要作用,而有36%的开发人员则认为Google+会扮演重要角色,虽然它的Google+用户群要小得多。66% 的开发人员认为随着Google+不断与范围更广的产品(如YouTube、GMail、Maps、Android)进行集成,它会成为Facebook 的一位强劲对手。
移动开发日渐崛起 . 超过50%的受访者打算在2012年投入更多的精力在移动开发方面,而相比2010年该数值只有27.4%。
iOS. Apple的操作系统吸引了众多眼球,89%的开发人员表示他们对iPhone开发感兴趣,而且88%的开发人员对iPad开发感兴趣。
Android. 虽然Android一个季度内开发人员的兴趣点下降了4.7%,但是总的百分比依旧牢固在78.6%。
Windows Phone 7. 虽然Windows Phone 7销售量依然不高,但开发人员对微软的移动操作系统兴趣丝毫未减。Windows Phone ,Android iPhone移动开发阵营将三雄争霸
BlackBerry. RIM操作系统兴趣点从2011年第四季度的20.7%下滑至15.5%。
云服务(Cloud Services). 开发人员对于移动应用程序中使用以下云服务感兴趣:35%的开发人员对位置服务(Location)感兴趣;33%的开发人员对通知服务 (Notification)感兴趣;11%的开发人员对评分及评论服务(Rating&Reviews)感兴趣;8%的开发人员对图片服务 (Photo)感兴趣 ;7%的开发人员对签到服务(Check-in)感兴趣;6%的开发人员对地点服务(Places)感兴趣。
最初,我是想实现一个可看见上传进度的效果。
在网络上查找了资料,资料虽少,不过稍加研究,还是实现了效果。
在此,先向前辈们表达敬意。
我将相关代码和思路进行一些整理,一并发出,方便大家参考。
1,需要依赖的包
apache-mime4j-0.6.jar;httpmime-4.0.2.jar
这两个包我一并上传。
2,核心类
ProcessEntity
继承自MultipartEntity。
为了获取上传进度,需要加入一个监听器以及自定义输出流。
package org.ashtray.single; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.ashtray.single.listener.ProgressListner; import org.ashtray.single.stream.CountingOutputStream; /** * 自定义文件上传实体类 * * @author Ashtray * @createtime 2012-9-29 上午11:36:05 最后修改时间 : 更新记录: */ public class ProcessEntity extends MultipartEntity { /**上传进度监听器*/ private ProgressListner listener; public ProcessEntity(ProgressListner listener) { super(); this.listener = listener; } public ProcessEntity(HttpMultipartMode mode, ProgressListner listener) { super(mode); this.listener = listener; } public ProcessEntity(HttpMultipartMode mode,String boundary,Charset charset,ProgressListner listener) { super(mode, boundary, charset); this.listener = listener; } @Override public void writeTo(OutputStream out) throws IOException { super.writeTo(new CountingOutputStream(out,listener)); } }
ProgressListner
上传进度监听器,上传文件时回调,获取当前上传字节数,更新UI进度条。
package org.ashtray.single.listener; /** * 监听器,上传文件时回调,获取当前上传字节数。 * * @author Ashtray * @createtime 2012-9-29 下午01:45:47 * 最后修改时间 : * 更新记录: */ public interface ProgressListner { /** * 文件上传过程中被回调, * 获取当前上传字节数。 * * @param count 当前上传字节数 * @author Ashtray * @createtime 2012-10-11 上午11:16:09 * 最后修改时间 : * 更新记录: */ void transferred(long count); }
CountingOutputStream
自定义输出流,继承自FilterOutputStream。
用于记录已上传的字节数,并告知监听器。
package org.ashtray.single.stream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import org.ashtray.single.listener.ProgressListner; /** * 自定义输出流, * 在文件上传时记录已上传的字节数, * 并回调监听器。 * * @author Ashtray * @createtime 2012-9-29 下午01:58:07 * 最后修改时间 : * 更新记录: */ public class CountingOutputStream extends FilterOutputStream { /**上传进度监听器*/ private ProgressListner listener; /**已上传字节数*/ private long transferred; public CountingOutputStream(OutputStream out,ProgressListner listener) { super(out); this.listener = listener; } @Override public void write(byte [] buffer,int offset,int count) throws IOException { out.write(buffer, offset, count); //记录已上传的字节数 transferred += count; //回调监听器方法 listener.transferred(transferred); } @Override public void write(int oneByte) throws IOException { out.write(oneByte); transferred++; listener.transferred(transferred); } }
HttpMultipartPost
文件上传异步任务,用于读取本地文件,创建并发送HTTP请求。
上传过程中更新UI。
package org.ashtray.single.async; import java.io.File; import java.io.IOException; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.DefaultHttpClient; import org.ashtray.single.Const; import org.ashtray.single.ProcessEntity; import org.ashtray.single.activity.ImageUpload; import org.ashtray.single.http.HttpCenter; import org.ashtray.single.listener.ProgressListner; import org.ashtray.single.util.Debug; import android.app.ProgressDialog; import android.os.AsyncTask; /** * 上传文件异步任务, * 便于实时更新UI中的进度条。 * * @author Ashtray * @createtime 2012-9-29 下午02:15:51 * 最后修改时间 : * 更新记录: */ public class HttpMultipartPost extends AsyncTask<Void, Integer, HttpResponse> { /**上下文*/ private ImageUpload activity; /**为简便,使用系统自带的进度条对话框*/ private ProgressDialog dialog; /**上传文件大小(实际上并非完全是待上传文件的大小,而是在文件基础上加入了部分描述信息)*/ private long size; public HttpMultipartPost(ImageUpload activity){ this.activity = activity; } @Override protected void onPreExecute() { //初始化进度对话框,并显示 dialog = new ProgressDialog(activity); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setMessage("Uploading Image"); dialog.setCancelable(true); dialog.show(); } @Override protected HttpResponse doInBackground(Void... params) { //创建HTTP请求 HttpClient client = new DefaultHttpClient(HttpCenter.getParams()); HttpPost post = new HttpPost(Const.URL_BYTE); //构建上传文件实体,注意匿名的监听器 ProcessEntity entity = new ProcessEntity(new ProgressListner() { @Override public void transferred(long count) { //此处必须这样,否则不能得出正确的计算结果 int progress = (int) ((count / (float) size) * 100); //更新UI publishProgress(progress); } }); try { //读取本地文件,并加入请求实体 File file = new File(Const.PATH); entity.addPart("file", new FileBody(file)); size = entity.getContentLength(); //注意此处的差值 Debug.log("文件:" + file.length() + "byte,请求:" + size + ",多余:" + (size - file.length())); //发送POST请求进行上传 post.setEntity(entity); HttpResponse resp = client.execute(post); return resp; } catch (ClientProtocolException e) { e.printStackTrace(); return null; } catch (IOException e) { e.printStackTrace(); return null; } } @Override protected void onProgressUpdate(Integer... values) { //更新进度 dialog.setProgress(values[0]); } @Override protected void onPostExecute(HttpResponse result) { dialog.dismiss(); } }
Activity略,只需调用new HttpMultipartPost(this).execute();即可。
3,服务端
服务端接收文件时会有一个问题,网络上的资料都没有提及。我找到了原因和一个不算很好的解决方法。
详见后述。
服务端即基本的Servlet即可。
package com.th.server.web.servlet; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 接收上传的文件,并保存在本地 * * @author Ashtray * @createtime 2012-9-25 下午05:02:53 * 最后修改时间 : * 更新记录: */ public class Upload extends HttpServlet { private static final long serialVersionUID = 2478185387706416494L; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setCharacterEncoding("UTF-8"); System.out.println(req.getContentType()); try { InputStream is = req.getInputStream(); OutputStream os = new FileOutputStream("D:/" + UUID.randomUUID() + ".jpg"); byte [] buffer = new byte [1024]; int length = -1; //以MultipartEntity的方式接收上传文件,流中会包含描述信息, //去掉第一次读取的内容,即可除去,保证只接收文件内容 boolean first = true; while ((length = is.read(buffer)) != -1) { if (!first) { os.write(buffer, 0, length); } first = false; } os.flush(); is.close(); os.close(); } catch (Exception e) { e.printStackTrace(); } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } }
如前文所述,采用MultipartEntity上传文件会产生一个问题。
会在请求中加入描述信息,导致Servlet中输出的文件会多余一部分内容,即前文中提到的一个差值。
用记事本将接收到的文件打开,会看到如下内容。
--aW3AQg4cMF7JA21OHAMMR9JaNlrj7nTAP
Content-Disposition: form-data; name="file"; filename="fe5bc4e1-8ecb-464c-9266-1794345a4a75"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
??5Exif...以下均为二进制内容
--aW3AQg4cMF7JA21OHAMMR9JaNlrj7nTAP--
--aW3AQg4cMF7JA21OHAMMR9JaNlrj7nTAP称为boundary,在服务端可通过req.getContentType()得到。
问题的关键就在于,如何去掉上传文件头部的那部分多余的内容。
本来希望由客户端提供多余内容的长度,服务端舍弃这一部分内容。
但是在实际测试中发现,并不需要如此精确,将第一次读取到的内容舍去即可。
如以上代码所示。
这样就能保证接收到文件正确,实际上还是有点小问题,文件尾部的boundary没有去掉,但是不会影响整个文件的正确性。
这个解决办法比较山寨,如有更好的办法,望不吝赐教。
完