2015年12月16日 星期三

【WebAPI】Custom ActionFilter Order


環境 : WebAPI 2.2 (詳細)

在WebAPI中,通常都繪有需要客製化ActionFilter的情況發生


但這些ActionFilter的執行順序卻是不固定的,並非排在越上面的越先執行,Filter有個FilterScope來表示他屬於何種層級的ActionFilter


執行順序依次是 Global , Controller , Action 。但如果是在同一層級的ActionFilter順序則未必按照固定。 可以由以下方式做個簡單的測試

先新增四個ActionFilter,




裡面全部都一樣,都繼承ActionFilterAttribute即可

public class FourAttribute :ActionFilterAttribute{ }



接著把OneAttribute註冊到Global



將剩下的分別註冊到Controller與Action上



在Get中補上以下程式然後執行看看

        [HttpGet]
        [Three]
        [Four]
        public IEnumerable<Tuple<string,string>> Get()
        {
            IHttpActionSelector actionSelector =
                this.Configuration.Services.GetActionSelector();

            HttpActionDescriptor actionDescriptor =
                actionSelector.SelectAction(this.ControllerContext);

            foreach (FilterInfo filterInfo in actionDescriptor.GetFilterPipeline())
            {
                yield return new Tuple<string, string>(
                    filterInfo.Instance.GetType().Name,
                    filterInfo.Scope.ToString()
                );
            }
        }


執行結果如下


順序的排序依據,只依照到所對應的層級,雖然這邊排序與我們在程式上的排序相同。但我這邊也實際碰到過順序變動過的狀況,為了避免這樣的問題,最好的方法就是在ActionFilter加上排序的屬性來解決。



  • 首先先做一個InterFace讓所有Attribute都實做它
    public interface IAttribute
        {
            int Order { get; set; }
        }
    

    public class FourAttribute : ActionFilterAttribute, IAttribute
        {
            public int Order { get; set; }
        }
    
  • CustomFilterInfo實做IComparable介面
    public class CustomFilterInfo : IComparable
        {
            public IFilter Instance { get; set; }
            public FilterScope Scope { get; set; }
    
            //FilterInfo
            public CustomFilterInfo(IFilter instance, FilterScope scope)
            {
                this.Instance = instance;
                this.Scope = scope;
            }
    
            public int CompareTo(object obj)
            {
                if (obj is CustomFilterInfo)
                {
                    var item = obj as CustomFilterInfo;
    
                    if (item.Instance is IAttribute)
                    {
                        var attr = item.Instance as IAttribute;
                        return (this.Instance as IAttribute).Order.CompareTo(attr.Order);
                    }
                }
    
                return 0;
            }
    
            public FilterInfo ConvertToFilterInfo()
            {
                return new FilterInfo(this.Instance, this.Scope);
            }
        }
    
  • CustomFilterProvider實做IFilterProvider介面
    public class CustomFilterProvider : IFilterProvider
        {
            public IEnumerable<FilterInfo> GetFilters(
                HttpConfiguration configuration, 
                HttpActionDescriptor actionDescriptor)
            {
                IEnumerable<CustomFilterInfo> customActionFilters =
                    actionDescriptor.GetFilters()
                                    .Select(i => new CustomFilterInfo(i, FilterScope.Controller));
    
                IEnumerable<CustomFilterInfo> customControllerFilters =
                    actionDescriptor.ControllerDescriptor
                                    .GetFilters()
                                    .Select(i => new CustomFilterInfo(i, FilterScope.Controller));
    
                return customControllerFilters.Concat(customActionFilters)
                                              .OrderBy(i => i)
                                              .Select(i => i.ConvertToFilterInfo());
    
            }
        }
    
  • 接著在Global將預設的FilterProvider移除,加上客製化的CustomFilterProvider
    protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
                GlobalConfiguration.Configure(WebApiConfig.Register);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
    
                //註冊Global Attribute
                GlobalConfiguration.Configuration.Filters.Add(new OneAttribute());
    
                
                //新增CustomFilterProvider
                GlobalConfiguration.Configuration.Services.Add(
                    typeof(System.Web.Http.Filters.IFilterProvider), new CustomFilterProvider());
    
                var providers = GlobalConfiguration.Configuration.Services.GetFilterProviders();
                var defaultprovider = providers.First(i => i is ActionDescriptorFilterProvider);
    
                //移除DefaultProvider
                GlobalConfiguration.Configuration.Services.Remove(
                    typeof(System.Web.Http.Filters.IFilterProvider),
                    defaultprovider);
            }
    
  • 回到剛剛的Action將Attibute補上Order再執行看看得到的順序
  • 結果可以看到Four排在Three的前面了,完成!!


2015年12月13日 星期日

【SQL】遞迴


有張表格如下,Parent欄位代表他的父層的RegionID,所以裡面包含所有縣市、行政區、鄉里等資料,那要如何用SQL遞迴的方式把台北市用階層的方式表列出來呢?
台北市 > 信義區、大安區、中正區.... > 港華里、老泉里.....



用CTE的寫法可以解決,把台北市當做茅點,然後Join自己即可達到遞迴的效果

with 遞迴 as(
------茅點 start-------
SELECT RegionID , Name
FROM Region a
WHERE RegionID = 1 --台北市
------茅點 end ---------
union all
select b.RegionID , b.name
from Region b
join 遞迴 on  b.Parent = 遞迴.RegionID
)
select * from 遞迴


搜尋出來的結果

2015年12月8日 星期二

AutoMapper與Json.NET JObject對應問題


使用套件:
Json.NET 7.0.1 : https://www.nuget.org/packages/Newtonsoft.Json/7.0.1
AutoMapper 4.04 : https://www.nuget.org/packages/AutoMapper/4.0.4

今天碰到一個問題,就是有個API回傳值的欄位是不固定無法掌握的,所以只好在轉型成強型別時以object當做該屬性的類別,但JsonConvert碰到類別為Object的東西就會轉成JObject ,而AutoMapper對應JObject會炸掉。以下是簡單時間的範例Code

void Main()
{
 Mapper.CreateMap<source, destination>()
 .ForMember(d => d.d_name, o => o.MapFrom(s => s.name))
 .ForMember(d => d.d_obj, o => o.MapFrom(s => s.obj));

 source test = new source 
 {
  name = "test",
  obj = new {code = 100},
 };

 var result = Mapper.Map<destination>(test);
 result.Dump();
}

public class source
{
 public string name { get; set; }
 public object obj { get; set; }
}

public class destination
{
 public string d_name { get; set; }
 public object d_obj { get; set; }
}

Source與Destination都有個property為object的類別屬性,在Main()裡面也想好兩個類別的對應關係,並且先準備好Source 然後透過AutoMapper轉出Result,在以上的範例執行正確沒問題




換個寫法

void Main()
{
 Mapper.CreateMap<source, destination>()
 .ForMember(d => d.d_name, o => o.MapFrom(s => s.name))
 .ForMember(d => d.d_obj, o => o.MapFrom(s => s.obj));

 source test = JsonConvert.DeserializeObject<source>("{\"name\":\"test\",\"obj\" : {\"code\":100}}");
 var result = Mapper.Map<destination>(test);
 result.Dump();
}

public class source
{
 public string name { get; set; }
 public object obj { get; set; }
}

public class destination
{
 public string d_name { get; set; }
 public object d_obj { get; set; }
}

差別只在於原本Source改成透過Json.Net由字串轉回來而已,這時候只要執行到AutoMapper那一行就會爆炸,錯誤訊息如下
AutoMapperMappingException: 

Mapping types:
JObject -> JObject
Newtonsoft.Json.Linq.JObject -> Newtonsoft.Json.Linq.JObject

Destination path:
destination.d_obj.d_obj

Source value:
{
  "code": 100
}


JsonConvert碰到目標為Object型別的欄位,會轉成JObject塞進去,AutoMapper用它來對應,
所以如果要解決這個問題需要做一些處理

//將Mapper改成如下
 Mapper.CreateMap<source, destination>()
 .ForMember(d => d.d_name, o => o.MapFrom(s => s.name))
 .ForMember(d => d.d_obj, o => o.ResolveUsing(s =>
 {
  if (s.obj is JObject)
  {
   var temp = s.obj as JObject;
   return temp.ToObject<Dictionary<string, object>>();
  }
  return s.obj;
 }));

這樣就可以正確地取出了

2015年12月3日 星期四

【MVC】EditorTemplate (二) 動態新增欄位


此篇範例程式 : 下載  (此範例在DynamicController之中)


呈上一篇,EditorTemplate (一) 我們可以來看到產生的原始碼如下







Name的部分是由 productList[index].屬性名組成,換句話說,如果今天要由前端動態新增一筆書籍資料,則必須按照這個規則編排下去,後端才能透過ViewModel的方式取得書籍資料










看起來一切美好圓滿,但如果今天的列表是可以新增之外,還要能動態刪除呢?
是不是我的[index]就要一直重新計算,不然送到後端就會不見了
*如果今天有4個textbox但是name分別是
productList[0].id
productList[1].id
productList[2].id
productList[5].id
這樣後端只會拿到0~2的ID,不按照順序編排的就會消失

還好.NET其實提供另外一種方式來繫結ViewModel
<input type="hidden" name="productList.Index" value="0072b890-0e1a-4c93-a5a7-9cafe84b65f8" /> 
<input  id="productList_0072b890-0e1a-4c93-a5a7-9cafe84b65f8__id" name="productList[0072b890-0e1a-4c93-a5a7-9cafe84b65f8].id" type="text" > 


這樣的話就可以不用管排序,自由的新增刪除List的項目,但EditorTemplate也需要改一改,不然EditTemplate產出的是[0]這種格是,但是前端產出的格是是[Guid],這樣會有問題。

所以EditTemplate改成用這個方法產出
*以下程式碼轉載自:  
搞搞就懂 - 點部落 :[ASP Net MVC] 如何綁定可動態新增或移除之資料集合(EditorTemplate)

  • 新增一組HtmlHelper
     public static MvcHtmlString EditorForMany<TModel, TValue>(
                this HtmlHelper<TModel> html,
                Expression<Func<TModel, IEnumerable<TValue>>> expression,
                string htmlFieldName = null) where TModel : class
            {
                var items = expression.Compile()(html.ViewData.Model);
                var sb = new StringBuilder();
                var hasPrefix = false;
    
                if (String.IsNullOrEmpty(htmlFieldName))
                {
                    var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
                    hasPrefix = !String.IsNullOrEmpty(prefix);
                    htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
                }
    
                if (items != null)
                {
                    foreach (var item in items)
                    {
                        var dummy = new { Item = item };
                        var guid = Guid.NewGuid().ToString();
    
                        var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
                        var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);
    
                        sb.Append(String.Format(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldName, guid));
                        sb.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", hasPrefix ? ExpressionHelper.GetExpressionText(expression) : htmlFieldName, guid)));
                    }
                }
                return new MvcHtmlString(sb.ToString());
            }
        }
    
  • 原本EditorFor改成自訂的EditorForMany
    @Html.EditorFor(x => x.productList)
    @Html.EditorForMany(x => x.productList)
    



這樣產出來的格式就會是帶Guid的方式了,可以正常跟前端結合了





2015年11月24日 星期二

【MVC】EditorTemplate (一)


Git範例檔下載位置 : https://github.com/toyo0103/Demo_EditTemplate_1

MVC在View的部分要呈現資料,通常是用ViewModel的方式來傳遞。當碰到List或是Array這種重複多筆的情況時,常常就會用迴圈的方式去跑

例如:

假設有個類別CategoryViewModel

public class CategoryViewModel
{
 public Guid id { get; set; }
 public string name { get; set; }
 public List<product> productList {get;set;}
}

public class product 
{
 public Guid id { get; set; }
 public string name {get;set;}
}


在View上要呈現CategoryViewModel.productList就可能用跑回圈的方式去組成
@model CategoryViewModel
@Html.TextBoxFor(x => x.id)
@Html.TextBoxFor(x => x.name)
//ProductList
@for(int i = 0; i < Model.productList.Count; i++)
{
    @Html.TextBoxFor(x => Model.productList[i].id)
    @Html.TextBoxFor(x => Model.productList[i].name)
}


這樣寫起View來有種又臭又長的感覺,所以可以使用MVC提供的EditorTemplate來解決,使用方法如下:


  • 在Views > Shared > 新增一個EditorTemplates資料
    *注意 這邊資料夾名稱一定要對
  • 接著在裡面新增一個product.cshtml,這邊一樣檔案名稱必須跟Class名稱相符才行
  • 然後開始編輯product.cshtml
    @model  product
    @Html.TextBoxFor(x => x.id)
    @Html.TextBoxFor(x => x.name)
    

  • 接著回到CategoryViewModel那邊迴圈的地方,把迴圈改成這一行即可
    @Html.EditorFor(x=>x.productList)
    

2015年10月22日 星期四

AutoMapper運用 (二)


如果是複雜Class要Mapper時
public class Category 
{
  public int id { get; set; }
  public List<product>  products {get;set;}
}

public class Product 
{
  public int id { get; set; }
  public string name { get; set; }
  public int qantity {get;set;}
}

Category要Mapping到CategoryViewModel

public class CategoryViewModel 
{
 public int id { get; set; }
 public List<Book>  books {get;set;}
}

public class Book 
{
 public string title {get;set;}

 public int number{get;set;}
}

有兩種做法,結果都會是一樣的



  1.  Category category = new Category()
     {
      id = 1,
      products = new List<Product>()
      {
       new Product()
       {
        id = 1,
        name = "西遊戲",
        qantity =10,
       },
       new Product()
       {
        id = 2,
        name = "三國志",
        qantity =30,
       },
       new Product()
       {
        id = 3,
        name = "鹿鼎記",
        qantity =50,
       }
      }
     };
    
     Mapper.CreateMap<Category, CategoryViewModel>()
      .ForMember(d => d.id, o => o.MapFrom(s => s.id))
      .ForMember(d => d.books, o => o.MapFrom(s => s.products));
    
     Mapper.CreateMap<Product, Book>()
      .ForMember(d => d.title, o => o.MapFrom(s => s.name))
      .ForMember(d => d.number, o => o.MapFrom(s => s.qantity));
    
     
     var viewModel = Mapper.Map<CategoryViewModel>(category);
     viewModel.Dump();
    

  2. void Main()
    {
       Category category = new Category()
       {
         id = 1,
         products = new List<Product>()
         {
           new Product()
           {
             id = 1,
             name = "西遊戲",
             qantity =10,
           },
           new Product()
           {
             id = 2,
             name = "三國志",
             qantity =30,
           },
           new Product()
           {
             id = 3,
             name = "鹿鼎記",
             qantity =50,
           }
        }
     };
    
     Mapper.CreateMap<Category, CategoryViewModel>()
      .ForMember(d => d.id, o => o.MapFrom(s => s.id))
      .ForMember(d => d.books, o => o.ResolveUsing<BookResolve>().FromMember(s => s.products));
    
     var viewModel = Mapper.Map<CategoryViewModel>(category);
     viewModel.Dump();
    }
    
    public class BookResolve : ValueResolver<IList<Product>, IList<Book>> 
    {
     protected override IList<Book> ResolveCore(IList<Product> source) 
     {
        List<Book> books = new List<Book>();
        foreach (var item in source)
        {
          var book = new Book() 
          {
            number = item.qantity,
            title = item.name
          };
          books.Add(book);
        }
        return books;
     }
    }
    

AutoMapper運用


寫網站常常會遇到從ViewModel對應到應用程式的DTO,或是DB的物件問題,以前自己的做法都是

public bool Login(LoginViewModel model)
{
   Account account = new Account()
   {
      Account = model.Account,
      Password = model.Password
   }
   var Result = _AccountService.Loign(account );
   return Result;
}

public class LoginViewModel
{
  public string Account{get;set;}
  public string Password{get;set;}
}


等到內部運作完畢的時候可能又要把對應的DTO處理成ViewModel在拋回頁面,這種反覆一直寫的動作其實真的很煩人,直到最近同事介紹了AutoMapper給我們之後,整個流程得到了大幅度的改善。


  • AutoMapper的基本運用

    當兩個物件的屬性都一樣時,AutoMapper會自動去對應並且轉出




    如果兩個物件的屬性對應略有不同,也可以加上規則

  • 如果兩的類別對應到同一個物件的情況

    void Main()
    {
     AccountViewModel model = new AccountViewModel() 
     {
      Account = "toyo",
      Password = "pwd",
      City = "Taipei",
      District = "大安"
     };
     var Group = new Group() 
     {
      ID = 1,
      Name = "客戶群組"
     };
     
     Mapper.CreateMap<AccountViewModel,AccountDTO>()
      .ForMember(d=> d.Address , o=>o.MapFrom(s=>string.Concat(s.City,s.District)));  
     var account = Mapper.Map<AccountDTO>(model);
    
        //這邊用上面已經轉好的account,來接續Mapper
     Mapper.CreateMap<Group, AccountDTO>()
      .ForMember(d => d.Account, o => o.Ignore())
      .ForMember(d => d.Password, o => o.Ignore())
      .ForMember(d => d.Address, o => o.Ignore())
      .ForMember(d => d.GroupName, o => o.MapFrom(s=>s.Name));
        //注意
     account = Mapper.Map<Group,AccountDTO>(Group,account);
    
     account.Dump();
    }
    
    public class AccountViewModel
    {
     public string Account { get; set; }
     public string Password { get; set; }
     public string City { get; set; }
     public string District {get;set;}
    }
    
    public class AccountDTO
    {
     public string Account { get; set; }
     public string Password { get; set; }
     public string Address { get; set; }
     public string GroupName {get;set;}
    }
    
    public class Group 
    {
     public int ID { get; set; }
     public string Name {get;set;}
    }
    

    結果


最後,在MVC的網站裡面使用AutoMapper可以在把所有規則都先寫在ProFile裡面,然後在Global一次註冊,之後在程式裡面用到就不用每次再寫CreateMap的部分

Profile :
public class ControllerMappingProfile : Profile
{
 public override string ProfileName
 {
  get
  {
   return "ControllerMappingProfile";
  }
 }

 protected override void Configure()
 {
  Mapper.CreateMap<AccountViewModel, AccountDTO>()
   .ForMember(d => d.Address, o => o.MapFrom(s => string.Concat(s.City, s.District)));

  Mapper.CreateMap<Group, AccountDTO>()
      .ForMember(d => d.Account, o => o.Ignore())
      .ForMember(d => d.Password, o => o.Ignore())
      .ForMember(d => d.Address, o => o.Ignore())
      .ForMember(d => d.GroupName, o => o.MapFrom(s => s.Name));
 }
}


AutoMapperConfig :
public class AutoMapperConfig
    {
        public static void Configure()
        {
            Mapper.Initialize(x =>
            {
                x.AddProfile<ControllerMappingProfile>();
            });
        }
    }


最後在Global Application_Start註冊即可
protected void Application_Start()
{
   AutoMapperConfig.Configure();
}

2015年10月21日 星期三

LinqToSql ExcuteQuery回傳Binary的問題


最近發現用LinqToSQL的物件模型,使用ExecuteQuery<Binary>的方式執行SQL會回傳
型別'System.Data.Linq.Binary' 必須宣告預設(無參數)建構涵式,才能在對應時加以建構,
的問題

實際案例如下:

  • 先在DB裡建立一個Function
    create function [dbo].[fb_CreatePWD]
    (
      @Param1 varchar(150)
    )
    RETURNS varbinary (150)
    AS
    BEGIN
      DECLARE @ResultVar varbinary(150)
      
      select @ResultVar = pwdencrypt(@Param1)
    
      RETURN @ResultVar
    END
    
    
    
  • 接著在MSSMS上測試是可以正常的

  • 然後移到LinqToSql的DataContext試試看
    var result = this.ExecuteQuery("select dbo.fn_CreatePWD({0})","ok").FirstOrDefault();
    
    得到以下錯誤訊息
    型別 'System.Data.Linq.Binary' 必須宣告預設 (無參數) 建構函式,才能在對應時加以建構

  • 網路上找到有人說用byte[]接可以,所以立馬做一下測試
    
    
    var result = this.ExecuteQuery("select dbo.fn_CreatePWD({0})","ok").FirstOrDefault();
    
    結果一樣GG, 型別 'System.Byte[]' 必須宣告預設 (無參數) 建構函式,才能在對應時加以建構。


  • 接著改用Entity Framework 6.0來試試看
  • 一樣不行
  • 換成Byte[]就可以了!!!

所以還是趕快放棄LinqToSql這個被放棄掉的產品,投入Entity Framwork的懷抱吧XD

*
這邊特別備註一下,如果今天是直接用物件模型將function拉進來,並且直接對xxxDataContext.fn_CreatePWD()操作是可以運作沒問題.

但因為我今天的狀況是在Repository Pattern的情況下,並不會真的對實體做操作,而是全部都對基底類別DataContext做執行,所以沒有這個Method可以使用

我想以上狀況應該是LinqToSql的Bug吧!!網路上也查了滿久的資料,好像沒有人對這塊有比較好的處理方式,如果有再補充上來~

2015年6月8日 星期一

[Android] Use Post To Call API


開發Android時,如何用Post的方式 Call API來取得值

  • 首先
                String UrlLocation = 你的API位置; //API位置
                String PostData = 要傳遞的資料(字串); //EX: ID=Toyo&Name=Steven
    
                HttpURLConnection conn = null;
                StringBuilder sb = new StringBuilder();
                try
                {
                    URL Url = new URL(UrlLocation);
                    conn = (HttpURLConnection)Url.openConnection();
                    conn.setRequestMethod("POST"); //要呼意的方式 Get Or Post
                    conn.setDoInput(true);
                    conn.setDoOutput(true);
                    conn.connect();
                    //開始傳輸資料過去給API
                    OutputStream Output = conn.getOutputStream();
                    BufferedWriter writer = new BufferedWriter(
                            new OutputStreamWriter(Output, "UTF-8"));
                    writer.write(PostData);
                    writer.flush();
                    writer.close();
                    Output.close();
                    
                    //讀取API回傳的值
                    BufferedReader br = new BufferedReader(new InputStreamReader(
                            conn.getInputStream(),"utf-8"));
    
                    String line;
                    while ((line=br.readLine())!=null)
                    {
                        sb.append(line);
                    }
                }
                catch(Exception ex)
                {
                    Log.e("API_Post",ex.getMessage());
                }
                finally
                {
                    if (conn != null)
                        conn.disconnect();
                }
    
                return sb.toString();
    
  • 目前測試的結果是,如果不把呼叫API用執行緒AsyncTask的方式處理,在conn.connect()這邊會跳出Http...InMainThread的Exception,原因是Andorid預設呼叫外部處理5秒沒有回應就會跳Exception,所以預設不管這支API反應速度快不快,他都要你把上面那段做成切執行緒的方式處理,當然網路上還是有舊的方式可以用,但既然這是官方目前推薦的做法就照做吧
    //這邊有三個參數
    //第一個參數String : 呼叫這個方法時要帶入的參數型態,這邊就是可以帶入String,
    //第二個參數Void : 處理時會回報狀態跟進度,這邊寫Void表示不回傳目前處理狀態
    //第三個參數String : 處理完成時回傳的值
    private class Post extends AsyncTask
        {
    
            //事件處理順序onPreExecute , doInBackground ,onPostExecute
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
            }
    
            @Override
            protected String doInBackground(String ... params) {
                //改寫這邊,呼叫方法時可以傳進多個String,所以這邊改抓params
                String UrlLocation = params[0]; //API位置
                String PostData = params[1]; //要傳資料
    
                HttpURLConnection conn = null;
                StringBuilder sb = new StringBuilder();
                try
                {
                    URL Url = new URL(UrlLocation);
                    conn = (HttpURLConnection)Url.openConnection();
                    conn.setRequestMethod("POST"); //要呼意的方式 Get Or Post
                    conn.setDoInput(true);
                    conn.setDoOutput(true);
                    conn.connect();
                    //開始傳輸資料過去給API
                    OutputStream Output = conn.getOutputStream();
                    BufferedWriter writer = new BufferedWriter(
                            new OutputStreamWriter(Output, "UTF-8"));
                    writer.write(PostData);
                    writer.flush();
                    writer.close();
                    Output.close();
    
                    //讀取API回傳的值
                    BufferedReader br = new BufferedReader(new InputStreamReader(
                            conn.getInputStream(),"utf-8"));
    
                    String line;
                    while ((line=br.readLine())!=null)
                    {
                        sb.append(line);
                    }
                }
                catch(Exception ex)
                {
                    Log.e("API_Post",ex.getMessage());
                }
                finally
                {
                    if (conn != null)
                        conn.disconnect();
                }
    
                return sb.toString();
            }
    
           @Override
            protected void onPostExecute(String Result) {
                super.onPostExecute(Result);
            }
        }
    
  • 最後,因為在Debug模式AsyncTask並不會進入,所以要在doInBackground的事件裡面加上這行,但因為非Debug模式下這行會造成Android沒有回應, 所以記得在部署時把這行拿掉!!
    android.os.Debug.waitForDebugger();
    
  • 全部完成後,呼叫方式如下
    String Url = "http://www.api.com.tw";
    String PostData = "id=toyo&name=steven";
    String Response = new Post().execute(Url,PostData).get();
    

2015年3月13日 星期五

[swift] CoreData (2) 新增,刪除CoreData資料


這邊做個簡單的介面,讓TableView的資料來自資料庫,並能把資料存到資料庫裡面

  • 首先先把TableView的資料來源設定成來自資料庫,完整程式碼如下,這樣就已經把Table的datasource friends來源改成SQLite了
            
    import UIKit
    import CoreData
    class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource, NSFetchedResultsControllerDelegate{
        @IBOutlet weak var tableview: UITableView!
        var friends : [Friend] = []
        var fetchRequestController:NSFetchedResultsController!
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //讀取Friend的Entity
            var fetchRequest = NSFetchRequest(entityName: "Friend")
            //排序方式用name這個欄位
            let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
            fetchRequest.sortDescriptors = [sortDescriptor]
            if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                fetchRequestController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
                //delegate設定為自己時,當新增修改刪除SQLite的資料時會呼叫以下事件
                //controllerWillChangeContent()
                //controller(_:didChangeObject:atIndexPath:forChangeType:newIndexPath:)
                //controllerDidChangeContent(_:)
                fetchRequestController.delegate = self
                var e:NSError?
                var result = fetchRequestController.performFetch(&e)
                friends = fetchRequestController.fetchedObjects as [Friend]
                
                if result != true{
                     println(e?.localizedDescription)
                }
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let identifier = "cell"
            var cell = tableView.dequeueReusableCellWithIdentifier(identifier) as customCell
            cell.nameLabel.text = friends[indexPath.row].name
            cell.telLabel.text = friends[indexPath.row].tel.stringValue
            cell.picImageView.image = UIImage(data: friends[indexPath.row].picture)
            cell.marryLabel.text = friends[indexPath.row].marry.boolValue ? "YES" : "NO"
            return cell
        }
    
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return friends.count
        }
    }
    
  • 接著寫按下save後的事件將檔案能寫到資料庫中,在這之前先處理Yes and no的按鈕和載入圖片按鈕
    Yes and No按鈕
            
        @IBOutlet weak var YesButton: UIButton!
        @IBOutlet weak var NoButton: UIButton!
    
        @IBAction func MarryButtonClick(sender: AnyObject) {
            YesButton.backgroundColor = (sender as UIButton == YesButton) ? UIColor.redColor() : UIColor.lightGrayColor()
            NoButton.backgroundColor = (sender as UIButton == NoButton) ? UIColor.redColor() : UIColor.lightGrayColor()
        }
    
    

    實作載入圖片的按鈕,需要讓viewController實作UIImagePickerControllerDelegate和UINavigationControllerDelegate
    class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource, NSFetchedResultsControllerDelegate , UIImagePickerControllerDelegate,UINavigationControllerDelegate
    {
    
    }
    
    selectPicture的按鈕
        @IBAction func SelectPictureClick(sender: AnyObject) {
            let imagePicker = UIImagePickerController()
            imagePicker.allowsEditing = false
            imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
            imagePicker.delegate = self
            //顯示挑選圖片的視窗
            self.presentViewController(imagePicker, animated: true, completion: nil)
        }
        
        func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
            PicImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
            PicImageView.contentMode = UIViewContentMode.ScaleAspectFill
            PicImageView.clipsToBounds = true
            
            //關閉挑選圖片的視窗
            dismissViewControllerAnimated(true, completion: nil)
        }
    
    
    接著執行看看應該結果
  • 接著把Save按鈕按下後存到SQLite的事件寫好
        @IBAction func SaveClick(sender: AnyObject) {
            if let manageObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                var friend = NSEntityDescription.insertNewObjectForEntityForName("Friend", inManagedObjectContext: manageObjectContext) as Friend
                friend.name = NameTextField.text
                friend.tel = TelTextField.text.toInt()
                friend.marry = (YesButton.backgroundColor == UIColor.redColor()) ? true : false
                friend.picture = UIImagePNGRepresentation(PicImageView.image)
                
                var e:NSError?
                if manageObjectContext.save(&e) != true{
                    println("error: \(e?.localizedDescription)")
                    return
                }
            }
        }
    
    接著執行看看會發現按了儲存按鈕後雖然存進資料庫了,但沒有讓TableView馬上連動顯示,必須重新開APP才會顯示在table上
  • 還記得之前有繼承NSFetchedResultsControllerDelegate嗎?這時候派上用場了,補上幾個事件讓他可以跟TableView連動吧
           
        //資料庫準備更新了
        func controllerWillChangeContent(controller: NSFetchedResultsController) {
            tableview.beginUpdates()
        }
        
        func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
            switch type{
            case .Insert:
                tableview.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete:
                tableview.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Update:
                tableview.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            default:
                tableview.reloadData()
            }
            
            friends = controller.fetchedObjects as [Friend]
        }
        
        func controllerDidChangeContent(controller: NSFetchedResultsController) {
            tableview.endUpdates()
        }
    
    
    接著再執行看看應該就可以看到他會馬上顯示了
  • 最後來個刪除吧!!這邊只舉例刪除該如何做,就不加到例子當中了
            if let manageObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                //簡單說就是挑出那個想刪除的物件
                var friendToDelete = self.fetchRequestController.objectAtIndexPath(NSIndexPath(index: 0)) as Friend
                //呼叫刪除方法把它丟進去
                manageObjectContext.deleteObject(friendToDelete)
                var e:NSError?
                //儲存
                if manageObjectContext.save(&e) != true{
                    println("error: \(e?.localizedDescription)")
                }
            }
    
    
  • 基本上有操作過.NET Entity物件的話,這邊的概念幾乎都雷同,最後補上完整的程式碼
    import UIKit
    import CoreData
    class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource, UINavigationControllerDelegate ,NSFetchedResultsControllerDelegate , UIImagePickerControllerDelegate{
        @IBOutlet weak var tableview: UITableView!
        var friends : [Friend] = []
        var fetchRequestController:NSFetchedResultsController!
        
        @IBOutlet weak var NameTextField: UITextField!
        @IBOutlet weak var TelTextField: UITextField!
        @IBOutlet weak var PicImageView: UIImageView!
        @IBOutlet weak var YesButton: UIButton!
        @IBOutlet weak var NoButton: UIButton!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //讀取Friend的Entity
            var fetchRequest = NSFetchRequest(entityName: "Friend")
            //排序方式用name這個欄位
            let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
            fetchRequest.sortDescriptors = [sortDescriptor]
            if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                fetchRequestController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
                //delegate設定為自己時,當新增修改刪除SQLite的資料時會呼叫以下事件
                //controllerWillChangeContent()
                //controller(_:didChangeObject:atIndexPath:forChangeType:newIndexPath:)
                //controllerDidChangeContent(_:)
                fetchRequestController.delegate = self
                var e:NSError?
                var result = fetchRequestController.performFetch(&e)
                friends = fetchRequestController.fetchedObjects as [Friend]
                
                if result != true{
                     println(e?.localizedDescription)
                }
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let identifier = "cell"
            var cell = tableView.dequeueReusableCellWithIdentifier(identifier) as customCell
            cell.nameLabel.text = friends[indexPath.row].name
            cell.telLabel.text = friends[indexPath.row].tel.stringValue
            cell.picImageView.image = UIImage(data: friends[indexPath.row].picture)
            cell.marryLabel.text = friends[indexPath.row].marry.boolValue ? "YES" : "NO"
            return cell
        }
    
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return friends.count
        }
        
        @IBAction func MarryButtonClick(sender: AnyObject) {
            YesButton.backgroundColor = (sender as UIButton == YesButton) ? UIColor.redColor() : UIColor.lightGrayColor()
            NoButton.backgroundColor = (sender as UIButton == NoButton) ? UIColor.redColor() : UIColor.lightGrayColor()
        }
        
        @IBAction func SelectPictureClick(sender: AnyObject) {
            let imagePicker = UIImagePickerController()
            imagePicker.allowsEditing = false
            imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
            imagePicker.delegate = self
            //顯示挑選圖片的視窗
            self.presentViewController(imagePicker, animated: true, completion: nil)
        }
        
        func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
            PicImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
            PicImageView.contentMode = UIViewContentMode.ScaleAspectFill
            PicImageView.clipsToBounds = true
            
            //關閉挑選圖片的視窗
            dismissViewControllerAnimated(true, completion: nil)
        }
        
        @IBAction func SaveClick(sender: AnyObject) {
            if let manageObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                var friend = NSEntityDescription.insertNewObjectForEntityForName("Friend", inManagedObjectContext: manageObjectContext) as Friend
                friend.name = NameTextField.text
                friend.tel = TelTextField.text.toInt()
                friend.marry = (YesButton.backgroundColor == UIColor.redColor()) ? true : false
                friend.picture = UIImagePNGRepresentation(PicImageView.image)
                
                var e:NSError?
                if manageObjectContext.save(&e) != true{
                    println("error: \(e?.localizedDescription)")
                    return
                }
            }
        }
        
        //資料庫準備更新了
        func controllerWillChangeContent(controller: NSFetchedResultsController) {
            tableview.beginUpdates()
        }
        
        func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
            switch type{
            case .Insert:
                tableview.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete:
                tableview.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Update:
                tableview.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            default:
                tableview.reloadData()
            }
            
            friends = controller.fetchedObjects as [Friend]
        }
        
        func controllerDidChangeContent(controller: NSFetchedResultsController) {
            tableview.endUpdates()
        }
    }
    
    
    

2015年3月12日 星期四

[swift] CoreData (1) 建立CoreData


CoreData是是一種以物件導向方式讓開發者與跟資料庫互動的框架,好處是你不需要懂SQL也能對SQLite做操作


  • 接著來筆記如何實作CoreData,首先先建立一個專案並把Use Core Data勾選起來
  • 接著打開AppDelegate最下面可以看到多了一大堆程式碼
    // MARK: - Core Data stack
    
        lazy var applicationDocumentsDirectory: NSURL = {
            // The directory the application uses to store the Core Data store file. This code uses a directory named "tw.com.xxx.Coredata" in the application's documents Application Support directory.
            let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
            return urls[urls.count-1] as NSURL
        }()
    
        lazy var managedObjectModel: NSManagedObjectModel = {
            // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
            let modelURL = NSBundle.mainBundle().URLForResource("Coredata", withExtension: "momd")!
            return NSManagedObjectModel(contentsOfURL: modelURL)!
        }()
    
        lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
            // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
            // Create the coordinator and store
            var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
            let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("Coredata.sqlite")
            var error: NSError? = nil
            var failureReason = "There was an error creating or loading the application's saved data."
            if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
                coordinator = nil
                // Report any error we got.
                let dict = NSMutableDictionary()
                dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
                dict[NSLocalizedFailureReasonErrorKey] = failureReason
                dict[NSUnderlyingErrorKey] = error
                error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
                // Replace this with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                NSLog("Unresolved error \(error), \(error!.userInfo)")
                abort()
            }
            
            return coordinator
        }()
    
        lazy var managedObjectContext: NSManagedObjectContext? = {
            // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
            let coordinator = self.persistentStoreCoordinator
            if coordinator == nil {
                return nil
            }
            var managedObjectContext = NSManagedObjectContext()
            managedObjectContext.persistentStoreCoordinator = coordinator
            return managedObjectContext
        }()
    
        // MARK: - Core Data Saving support
    
        func saveContext () {
            if let moc = self.managedObjectContext {
                var error: NSError? = nil
                if moc.hasChanges && !moc.save(&error) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                    NSLog("Unresolved error \(error), \(error!.userInfo)")
                    abort()
                }
            }
        }
    
  • 接著建立物件託管模型
  • 接著建立Entities

    圖片需要用Binary Data來儲存
  • 接著建立Class來對應Entity
    import Foundation
    import CoreData
    class Friend:NSManagedObject  {
        @NSManaged var name:String!
        @NSManaged var tel:NSNumber!
        @NSManaged var picture:NSData!
        @NSManaged var marry:NSNumber!
    }
    
    *對應Entity中的Boolean, Integer, Float and Double attributes,都是用NSNumber
  • 接著設定Entity與Class的對應關係,如圖:

    這樣託管物件的建立就已經完成了,下一篇介紹如何叫用

2015年3月11日 星期三

[swift] IOS地圖運用 (6) 透過CLGeocoder轉換經緯度與地址


IOS提供CLGeocoder將地址轉成經緯度,或是把經緯度轉換成地址,詳細語法如下

  • 記得先Import MapKit
  • 地址轉經緯度(geocodeAddressString)
    let geoCoder = CLGeocoder()
    geoCoder.geocodeAddressString("這邊帶入地址", completionHandler: {
       (placemarks:[AnyObject]!,error:NSError!) -> Void in
       if error != nil{
          println(error)
          return
       }
                
       if placemarks != nil && placemarks.count > 0{
          let placemark = placemarks[0] as CLPlacemark
          //placemark.location.coordinate 取得經緯度的參數            
       }
    })
    
  • 經緯度轉地址的方法
    let geoCoder = CLGeocoder()
    geoCoder.reverseGeocodeLocation(CLLocation(latitude: 25.024839 , longitude: 121.549170), completionHandler: {
       (placemarks:[AnyObject]!,error:NSError!) -> Void in
       if error != nil{
          println(error)
          return
       }
    
       //name         街道地址
       //country      國家
       //province     省
       //locality     市
       //sublocality  縣.區
       //route        街道、路
       //streetNumber 門牌號碼
       //postalCode   郵遞區號
       if placemarks != nil && placemarks.count > 0{
          let placemark = placemarks[0] as CLPlacemark
          //這邊拼湊轉回來的地址
          //placemark.name
       }
    })
    

[swift] 製作動畫


目標結果如圖


  • 首先先把最後所有按鈕要放的位置擺好
  • 把每個按鈕跟ViewController做對應
        @IBOutlet weak var backgroundImageView: UIImageView!
        @IBOutlet weak var fbButton: UIButton!
        @IBOutlet weak var twitterButton: UIButton!
        @IBOutlet weak var messageButton: UIButton!
        @IBOutlet weak var mailButton: UIButton!
    
  • 接著用CGAffineTransformMakeTranslation的方式將所有按鈕移到動畫的起始位置,
    這邊解釋一下程式中那些數字怎麼決定的,CGAffineTransformMakeTranslation需要給定x y值,如果(0, -300)表示x軸不動,Y軸比原本你在StoryBoard放的位置還要往上移300個點的位置,反之(0, 300)則表示比原本擺放的位置往下移動300點
        
        override func viewDidLoad() {
            super.viewDidLoad()
            self.fbButton.transform = CGAffineTransformMakeTranslation(0, -300)
            self.messageButton.transform = CGAffineTransformMakeTranslation(0, -364)
            
            self.mailButton.transform = CGAffineTransformMakeTranslation(0, 300)
            self.twitterButton.transform = CGAffineTransformMakeTranslation(0, 364)
        }
    
  • 接著大家測試一下程式,應該會發現東西的確是跑出螢幕了,接著在viewDidAppear補上以下程式碼就大功告成了!!
        override func viewDidAppear(animated: Bool) {
            super.viewDidAppear(animated)
    
            //動畫,分成兩個區塊是因為有快有慢,差別在於delay多久再發動,usingSpringWithDamping是彈跳的反作用力,越小越劇烈
            UIView.animateWithDuration(0.9, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: nil, animations: {
                //跑回原來的點
                let translate = CGAffineTransformMakeTranslation(0, 0)
                self.messageButton.transform = translate
                self.twitterButton.transform = translate
            }, completion: nil)
            
            UIView.animateWithDuration(0.9, delay: 0.1, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: nil, animations: {
                let translate = CGAffineTransformMakeTranslation(0, 0)
                self.fbButton.transform = translate
                self.mailButton.transform = translate
            }, completion: nil)
        }
    

2015年3月10日 星期二

[swift] 將圖片做出霧化效果


以下範例是參考做成的筆記:養成iOS8 App程式設計實力的25堂課:最新Swift開發教學

  • IOS8有提供將圖片霧化的效果,如果我們將一張圖片擺進都不調整的情況下看到的結果如下
  • 如果我們想把它變成這樣呢?
  • 程式碼撰寫如下:
        @IBOutlet weak var backgroundImageView: UIImageView!//背景圖   
        override func viewDidLoad() {
            super.viewDidLoad()
            //.Dark等於圖1
            //.ExtraLight等於圖2
            //.Light等於圖3
            var blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Light)
            var blurEffectView = UIVisualEffectView(effect: blurEffect)
            blurEffectView.frame = view.bounds
            backgroundImageView.addSubview(blurEffectView)
        }
    

2015年3月9日 星期一

[swift] 製作IOS Table (5) UITableViewRowAction


開始之前想推薦這本書:養成iOS8 App程式設計實力的25堂課:最新Swift開發教學,只能說看了對於初學者的我來說受益良多,而且對於IOS很多觀念也更加清楚了

  • 這邊要實作的功能如下圖
  • IOS8的TableView當中,有個可以往左滑可以選擇更多動作的功能,Apple官方有提供相對應的事件可以實作它
  • 首先先Override TableView commitEditingStyle這個事件,什麼都先不要做就執行看看
    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
            
    }
    
    立馬預設的Delete按鈕就跑出來了
  • 接著補上以下code,再實際執行看看就可以發現該列可以刪除了
       func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
            if editingStyle == UITableViewCellEditingStyle.Delete{
                //將TableView的DataSource刪除
                self.restaurantArray.removeAtIndex(indexPath.row)
                //將TableView該列刪除
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }
        }
    
  • 但如果要客製更多按鈕呢?接下來改寫 editActionsForRowAtIndexPath這個事件,但需要特別注意的是如果我們新增了editActionsForRowAtIndexPath這個事件,則IOS就不會預設幫我們增加Delete的按鈕,所以Delete的按鈕也得自己做瞜~

    先把commitEditingStyle恢復成空值,只讓IOS知道我們要左滑出現功能列就好
       func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
          //把剛剛寫的都刪光光
       }
    
    接著改寫editActionsForRowAtIndexPath
        func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
            var shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Share", handler: nil)
            
            //delete按鈕自己做   
            var deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler: {
                (action:UITableViewRowAction! , indexPath:NSIndexPath!) -> Void in
                self.restaurantArray.removeAtIndex(indexPath.row)
                self.restaurantTable.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            })
    
            return [deleteAction,shareAction]
        }
    
    執行看看就可以看到多出了一過按鈕了,如果想改變按鈕的顏色可以這樣寫
    shareAction.backgroundColor = UIColor(red: 255/255, green: 166/255, blue: 51/255, alpha: 1)
    
  • 最後就可以得出圖1的結果啦!!!!

[swift] UIAlertController


  • APP常常有一種狀況是當使用者按下某個選項時,從下面滑出更多的選項讓它做進一步動作,例如在iphone的照片點選垃圾桶時

  • 在IOS 8中多了一個UIAlertController的類別來取代UIAlerView,實際作法如下
    首先將程式碼加到viewDidAppear,否則無法顯示效果,原因請看IOS生命週期的說明點我
        override func viewDidAppear(animated: Bool) {
            //建立UIAlertController
            let quetion = UIAlertController(title: nil, message: "What Do u want?", preferredStyle: .ActionSheet);
            
            //新增選項
            let callaction = UIAlertAction(title: "call xxx", style: .Default , handler:nil);
            
            //把選項加到UIAlertController
            quetion.addAction(callaction);
            //Show
            self.presentViewController(quetion, animated: true, completion: nil);
        }
    
    
    執行看到的結果如下圖
  • 如果將ActionSheet改成Alert,則結果
    let quetion = UIAlertController(title: nil, message: "What Do u want?", preferredStyle: .Alert);
    
  • 如果更進階一些,在選擇選項後有些回應時
    我們拉一個label到ViewController裡面來,並且在按下call xxx選項時,把label的字樣給改變
    class AlertViewController: UIViewController {
        @IBOutlet weak var showaction: UILabel!
        override func viewDidLoad() {
            super.viewDidLoad();
            
        }
        
        override func viewDidAppear(animated: Bool) {
            let quetion = UIAlertController(title: nil, message: "What Do u want?", preferredStyle: .Alert);
            
            
            let callaction = UIAlertAction(title: "call xxx",style: .Default, handler:{
                (action:UIAlertAction!) -> Void in
                self.showaction.text = "Do call xxx";
            });
            
            quetion.addAction(callaction);
            self.presentViewController(quetion, animated: true, completion: nil);
        }
    }
    
    執行看看吧!!

2015年3月8日 星期日

[swift] String To MD5


  • 我將這個Function寫在extension裡面,首先Swift要能用MD5的function必須先建立header檔並掛上(header檔如何建立請看這篇)
    #import <CommonCrypto/CommonCrypto.h>
    
  • 然後
        
    func md5() -> String! {
            let str = self.cStringUsingEncoding(NSUTF8StringEncoding)
            let strLen = CUnsignedInt(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
            let digestLen = Int(CC_MD5_DIGEST_LENGTH)
            let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
            
            CC_MD5(str!, strLen, result)
            
            var hash = NSMutableString()
            for i in 0..<digestLen {
                hash.appendFormat("%02x", result[i])
            }
            
            result.destroy()
            
            return String(format: hash) 
    }       
    

[swift] Extension


Swift這個語法常常會遇到參數型態間的轉換,寫慣了C#,常常遇到什麼參數轉換就會想要.toString() .toInt()阿之類的,偏偏Swift沒有這些function不打緊,還每種型態的變數互轉常常方式不一樣....搞得我每次遇到都要查一下到底這次又該用哪個function來轉換好,最後索性就把這些整理起來做成Extension,以後就跟C#一樣to啥to啥的就好.


  • 那接下來就來介紹怎麼擴充Swift的function語法
  • extension是關鍵字,擴充String類別型態的function
    extension String{
        func toDoublue() -> Double{
            return (self as NSString).doubleValue;
        }
    }
    
  • 結果

2015年3月6日 星期五

[SQL] 2008 R2 繁體中文版安裝問題


如果你作業系統是從英文版安裝與語系版本成為中文版的話,安裝SQL繁體中文版應該會遇到不明的錯誤而停止,上網找了一下資料發現這篇:
解決 Windows 7 線上更新語言包後安裝 SQL Server 2008 R2 繁體中文版失敗的問題

原因是從英文版透過語系包轉成的中文版的作業系統,他是香港繁體中文版,在安裝SQL時預設會去抓3076_ZHH_LP這個資料夾,但繁體中文版的SQL Server的資料夾為1028_CHT_LP。

所以解決方法就是,把全部的光碟內容複製下來,並且將1028_CHT_LP複製一份並且改名為3076_ZHH_LP,用這個資料夾安裝就可以順利完成了

2015年3月5日 星期四

[swift] NetWork (4) 解析Json


  • 先做一個json檔案來模擬接到時怎麼解析
    {
    "組織名稱":"普攏拱團隊",
    "組織成員":[
        {"姓名":"科科人","年齡":11},
        {"姓名":"可可狗","年齡":120}
    ]
    }
    
  • 在viewDidLoad加上,測試看看!!
            //將檔案讀出來
            let path = NSBundle.mainBundle().pathForResource("json", ofType: "txt");
            var data:NSData = NSData(contentsOfFile: path!)!;
            
            //解析Json
            var jsonobj = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary;
            println(jsonobj["組織名稱"] as String);
            println("組織成員");
            var arr = jsonobj["組織成員"] as NSArray;
            for i in (0...arr.count - 1){
                let mdic = arr[i] as NSDictionary;
                println(mdic["姓名"] as String);
                println(mdic["年齡"] as Int);
            }
    
    

2015年3月4日 星期三

[swift] NetWork (3) 判斷手機是否有網路連線可用


主要參考兩篇文章做修改:
Detect Internet status programmatically using Swift and Object-C language
[iOS] 即時判斷網路連線狀態 (Detect Network Status on Real-Time)


  • Apple已經用Objective-C寫了判斷網路的套件給大家使用,所以接下來會解說如何用Swift專案載入Objective-C套件
  • 先到這邊下載Reachability
  • 接著把Reachability專案中的Reachability.m和Reachability.h抓到專案中
  • 在專案中新增Header檔

  • 打開Header檔,把Reachability.h掛進去,這樣以後專案中引用Reachability裡面的class都不再Import了
  • 把Header檔加到專案的Build Setting中
  • 打開Reachability.m找到(void)dealloc改成
    - (void)dealloc
    {
        [self stopNotifier];
        if (_reachabilityRef != NULL)
        {
     CFRelease(_reachabilityRef);
        }
        [super dealloc];
    }
    
  • 到專案中的Build Phases,把Reachability.m加上-fno-objc-arc
  • 以上就全部掛載完成了,接著來看看這個套件如何使用,並讓它告知我們網路狀態
    打開view controller,新增一個監測網路的變數,並且在viewDidLoad寫下
        var internetReachability:Reachability!; //網路狀態監控
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //在通知中心註冊事件,當網路狀態有變動的時候會觸發
            NSNotificationCenter.defaultCenter().addObserverForName(kReachabilityChangedNotification, object: nil, queue: NSOperationQueue.mainQueue()) { (NSNotification) -> Void in
                let networksStatus: NetworkStatus = self.internetReachability.currentReachabilityStatus()
                var status: String!
                if networksStatus.value == 0 {
                    status = "Disconnection"
                } else if networksStatus.value == 1 {
                    status = "Connection"
                } else {
                    status = "Connection"
                }
                UIAlertView(title: "網路狀態", message: status, delegate: nil, cancelButtonTitle: "OK").show();
            }
            
            self.internetReachability = Reachability.reachabilityForInternetConnection();
            //開始監控狀況
            self.internetReachability.startNotifier()
       }
    
    在Mac上面執行看看,測試方法就是把mac的wifi連線給關閉,就會看到App跳出提示訊息了
  • 接下來把viewDidLoad改成如下,這段是告訴你目前使用的網路是什麼?Wifi or 3G之類的
    override func viewDidLoad() {
            super.viewDidLoad()
                  
            let statusReach: Reachability = Reachability.reachabilityForInternetConnection()
            let networksStatus: NetworkStatus = statusReach.currentReachabilityStatus()
            var status: String!
            if networksStatus.value == 0 {
                status = "NoReachable"
            } else if networksStatus.value == 1 {
                status = "ReachableViaWiFi"
            } else {
                status = "ReachableViaWWAN"
            }
            
            UIAlertView(title: "網路狀態", message: status, delegate: nil, cancelButtonTitle: "OK").show();
        }
    
    執行看看